1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.Intent.ShortcutIconResource;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.content.pm.ResolveInfo;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.net.Uri;
36 import android.os.Build;
37 import android.os.Environment;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Parcelable;
42 import android.os.Process;
43 import android.os.SystemClock;
44 import android.os.TransactionTooLargeException;
45 import android.provider.BaseColumns;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.LongSparseArray;
49 import android.util.Pair;
50
51 import com.android.launcher3.compat.AppWidgetManagerCompat;
52 import com.android.launcher3.compat.LauncherActivityInfoCompat;
53 import com.android.launcher3.compat.LauncherAppsCompat;
54 import com.android.launcher3.compat.PackageInstallerCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.UserHandleCompat;
57 import com.android.launcher3.compat.UserManagerCompat;
58 import com.android.launcher3.model.WidgetsModel;
59 import com.android.launcher3.util.ComponentKey;
60 import com.android.launcher3.util.CursorIconInfo;
61 import com.android.launcher3.util.LongArrayMap;
62 import com.android.launcher3.util.ManagedProfileHeuristic;
63 import com.android.launcher3.util.Thunk;
64
65 import java.lang.ref.WeakReference;
66 import java.net.URISyntaxException;
67 import java.security.InvalidParameterException;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map.Entry;
78 import java.util.Set;
79
80 /**
81 * Maintains in-memory state of the Launcher. It is expected that there should be only one
82 * LauncherModel object held in a static. Also provide APIs for updating the database state
83 * for the Launcher.
84 */
85 public class LauncherModel extends BroadcastReceiver
86 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
87 static final boolean DEBUG_LOADERS = false;
88 private static final boolean DEBUG_RECEIVER = false;
89 private static final boolean REMOVE_UNRESTORED_ICONS = true;
90
91 static final String TAG = "Launcher.Model";
92
93 public static final int LOADER_FLAG_NONE = 0;
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
96
97 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
98 private static final long INVALID_SCREEN_ID = -1L;
99
100 @Thunk final boolean mAppsCanBeOnRemoveableStorage;
101 private final boolean mOldContentProviderExists;
102
103 @Thunk final LauncherAppState mApp;
104 @Thunk final Object mLock = new Object();
105 @Thunk DeferredHandler mHandler = new DeferredHandler();
106 @Thunk LoaderTask mLoaderTask;
107 @Thunk boolean mIsLoaderTaskRunning;
108 @Thunk boolean mHasLoaderCompletedOnce;
109
110 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
111
112 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
113 static {
114 sWorkerThread.start();
115 }
116 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
117
118 // We start off with everything not loaded. After that, we assume that
119 // our monitoring of the package manager provides all updates and we never
120 // need to do a requery. These are only ever touched from the loader thread.
121 @Thunk boolean mWorkspaceLoaded;
122 @Thunk boolean mAllAppsLoaded;
123
124 // When we are loading pages synchronously, we can't just post the binding of items on the side
125 // pages as this delays the rotation process. Instead, we wait for a callback from the first
126 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
127 // a normal load, we also clear this set of Runnables.
128 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
129
130 /**
131 * Set of runnables to be called on the background thread after the workspace binding
132 * is complete.
133 */
134 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
135
136 @Thunk WeakReference<Callbacks> mCallbacks;
137
138 // < only access in worker thread >
139 AllAppsList mBgAllAppsList;
140 // Entire list of widgets.
141 WidgetsModel mBgWidgetsModel;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170
171 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
172 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
173 new HashMap<UserHandleCompat, HashSet<String>>();
174
175 // </ only access in worker thread >
176
177 @Thunk IconCache mIconCache;
178
179 @Thunk final LauncherAppsCompat mLauncherApps;
180 @Thunk final UserManagerCompat mUserManager;
181
182 public interface Callbacks {
183 public boolean setLoadOnResume();
184 public int getCurrentWorkspaceScreen();
185 public void startBinding();
186 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
187 boolean forceAnimateIcons);
188 public void bindScreens(ArrayList<Long> orderedScreenIds);
189 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
190 public void bindFolders(LongArrayMap<FolderInfo> folders);
191 public void finishBindingItems();
192 public void bindAppWidget(LauncherAppWidgetInfo info);
193 public void bindAllApplications(ArrayList<AppInfo> apps);
194 public void bindAppsAdded(ArrayList<Long> newScreens,
195 ArrayList<ItemInfo> addNotAnimated,
196 ArrayList<ItemInfo> addAnimated,
197 ArrayList<AppInfo> addedApps);
198 public void bindAppsUpdated(ArrayList<AppInfo> apps);
199 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
200 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
201 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
202 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
203 public void bindComponentsRemoved(ArrayList<String> packageNames,
204 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
205 public void bindAllPackages(WidgetsModel model);
206 public void bindSearchablesChanged();
207 public boolean isAllAppsButtonRank(int rank);
208 public void onPageBoundSynchronously(int page);
209 public void dumpLogsToLocalData();
210 }
211
212 public interface ItemInfoFilter {
213 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
214 }
215
216 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
217 Context context = app.getContext();
218
219 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
220 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
221 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
222 // resource string.
223 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
224 ProviderInfo providerInfo =
225 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
226 ProviderInfo redirectProvider =
227 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
228
229 Log.d(TAG, "Old launcher provider: " + oldProvider);
230 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
231
232 if (mOldContentProviderExists) {
233 Log.d(TAG, "Old launcher provider exists.");
234 } else {
235 Log.d(TAG, "Old launcher provider does not exist.");
236 }
237
238 mApp = app;
239 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
240 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
241 mIconCache = iconCache;
242
243 mLauncherApps = LauncherAppsCompat.getInstance(context);
244 mUserManager = UserManagerCompat.getInstance(context);
245 }
246
247 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
248 * posted on the main thread handler. */
249 @Thunk void runOnMainThread(Runnable r) {
250 if (sWorkerThread.getThreadId() == Process.myTid()) {
251 // If we are on the worker thread, post onto the main handler
252 mHandler.post(r);
253 } else {
254 r.run();
255 }
256 }
257
258 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
259 * posted on the worker thread handler. */
260 private static void runOnWorkerThread(Runnable r) {
261 if (sWorkerThread.getThreadId() == Process.myTid()) {
262 r.run();
263 } else {
264 // If we are not on the worker thread, then post to the worker handler
265 sWorker.post(r);
266 }
267 }
268
269 /**
270 * Runs the specified runnable after the loader is complete
271 */
272 @Thunk void runAfterBindCompletes(Runnable r) {
273 if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
274 synchronized (mBindCompleteRunnables) {
275 mBindCompleteRunnables.add(r);
276 }
277 } else {
278 runOnWorkerThread(r);
279 }
280 }
281
282 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
283 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
284 }
285
286 public void setPackageState(final PackageInstallInfo installInfo) {
287 Runnable updateRunnable = new Runnable() {
288
289 @Override
290 public void run() {
291 synchronized (sBgLock) {
292 final HashSet<ItemInfo> updates = new HashSet<>();
293
294 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
295 // Ignore install success events as they are handled by Package add events.
296 return;
297 }
298
299 for (ItemInfo info : sBgItemsIdMap) {
300 if (info instanceof ShortcutInfo) {
301 ShortcutInfo si = (ShortcutInfo) info;
302 ComponentName cn = si.getTargetComponent();
303 if (si.isPromise() && (cn != null)
304 && installInfo.packageName.equals(cn.getPackageName())) {
305 si.setInstallProgress(installInfo.progress);
306
307 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
308 // Mark this info as broken.
309 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
310 }
311 updates.add(si);
312 }
313 }
314 }
315
316 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
317 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
318 widget.installProgress = installInfo.progress;
319 updates.add(widget);
320 }
321 }
322
323 if (!updates.isEmpty()) {
324 // Push changes to the callback.
325 Runnable r = new Runnable() {
326 public void run() {
327 Callbacks callbacks = getCallback();
328 if (callbacks != null) {
329 callbacks.bindRestoreItemsChange(updates);
330 }
331 }
332 };
333 mHandler.post(r);
334 }
335 }
336 }
337 };
338 runOnWorkerThread(updateRunnable);
339 }
340
341 /**
342 * Updates the icons and label of all pending icons for the provided package name.
343 */
344 public void updateSessionDisplayInfo(final String packageName) {
345 Runnable updateRunnable = new Runnable() {
346
347 @Override
348 public void run() {
349 synchronized (sBgLock) {
350 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
351 final UserHandleCompat user = UserHandleCompat.myUserHandle();
352
353 for (ItemInfo info : sBgItemsIdMap) {
354 if (info instanceof ShortcutInfo) {
355 ShortcutInfo si = (ShortcutInfo) info;
356 ComponentName cn = si.getTargetComponent();
357 if (si.isPromise() && (cn != null)
358 && packageName.equals(cn.getPackageName())) {
359 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
360 // For auto install apps update the icon as well as label.
361 mIconCache.getTitleAndIcon(si,
362 si.promisedIntent, user,
363 si.shouldUseLowResIcon());
364 } else {
365 // Only update the icon for restored apps.
366 si.updateIcon(mIconCache);
367 }
368 updates.add(si);
369 }
370 }
371 }
372
373 if (!updates.isEmpty()) {
374 // Push changes to the callback.
375 Runnable r = new Runnable() {
376 public void run() {
377 Callbacks callbacks = getCallback();
378 if (callbacks != null) {
379 callbacks.bindShortcutsChanged(updates,
380 new ArrayList<ShortcutInfo>(), user);
381 }
382 }
383 };
384 mHandler.post(r);
385 }
386 }
387 }
388 };
389 runOnWorkerThread(updateRunnable);
390 }
391
392 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
393 final Callbacks callbacks = getCallback();
394
395 if (allAppsApps == null) {
396 throw new RuntimeException("allAppsApps must not be null");
397 }
398 if (allAppsApps.isEmpty()) {
399 return;
400 }
401
402 // Process the newly added applications and add them to the database first
403 Runnable r = new Runnable() {
404 public void run() {
405 runOnMainThread(new Runnable() {
406 public void run() {
407 Callbacks cb = getCallback();
408 if (callbacks == cb && cb != null) {
409 callbacks.bindAppsAdded(null, null, null, allAppsApps);
410 }
411 }
412 });
413 }
414 };
415 runOnWorkerThread(r);
416 }
417
418 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
419 int[] xy, int spanX, int spanY) {
420 LauncherAppState app = LauncherAppState.getInstance();
421 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
422 final int xCount = (int) profile.numColumns;
423 final int yCount = (int) profile.numRows;
424 boolean[][] occupied = new boolean[xCount][yCount];
425 if (occupiedPos != null) {
426 for (ItemInfo r : occupiedPos) {
427 int right = r.cellX + r.spanX;
428 int bottom = r.cellY + r.spanY;
429 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
430 for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
431 occupied[x][y] = true;
432 }
433 }
434 }
435 }
436 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
437 }
438
439 /**
440 * Find a position on the screen for the given size or adds a new screen.
441 * @return screenId and the coordinates for the item.
442 */
443 @Thunk Pair<Long, int[]> findSpaceForItem(
444 Context context,
445 ArrayList<Long> workspaceScreens,
446 ArrayList<Long> addedWorkspaceScreensFinal,
447 int spanX, int spanY) {
448 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
449
450 // Use sBgItemsIdMap as all the items are already loaded.
451 assertWorkspaceLoaded();
452 synchronized (sBgLock) {
453 for (ItemInfo info : sBgItemsIdMap) {
454 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
455 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
456 if (items == null) {
457 items = new ArrayList<>();
458 screenItems.put(info.screenId, items);
459 }
460 items.add(info);
461 }
462 }
463 }
464
465 // Find appropriate space for the item.
466 long screenId = 0;
467 int[] cordinates = new int[2];
468 boolean found = false;
469
470 int screenCount = workspaceScreens.size();
471 // First check the preferred screen.
472 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
473 if (preferredScreenIndex < screenCount) {
474 screenId = workspaceScreens.get(preferredScreenIndex);
475 found = findNextAvailableIconSpaceInScreen(
476 screenItems.get(screenId), cordinates, spanX, spanY);
477 }
478
479 if (!found) {
480 // Search on any of the screens starting from the first screen.
481 for (int screen = 1; screen < screenCount; screen++) {
482 screenId = workspaceScreens.get(screen);
483 if (findNextAvailableIconSpaceInScreen(
484 screenItems.get(screenId), cordinates, spanX, spanY)) {
485 // We found a space for it
486 found = true;
487 break;
488 }
489 }
490 }
491
492 if (!found) {
493 // Still no position found. Add a new screen to the end.
494 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
495
496 // Save the screen id for binding in the workspace
497 workspaceScreens.add(screenId);
498 addedWorkspaceScreensFinal.add(screenId);
499
500 // If we still can't find an empty space, then God help us all!!!
501 if (!findNextAvailableIconSpaceInScreen(
502 screenItems.get(screenId), cordinates, spanX, spanY)) {
503 throw new RuntimeException("Can't find space to add the item");
504 }
505 }
506 return Pair.create(screenId, cordinates);
507 }
508
509 /**
510 * Adds the provided items to the workspace.
511 */
512 public void addAndBindAddedWorkspaceItems(final Context context,
513 final ArrayList<? extends ItemInfo> workspaceApps) {
514 final Callbacks callbacks = getCallback();
515 if (workspaceApps.isEmpty()) {
516 return;
517 }
518 // Process the newly added applications and add them to the database first
519 Runnable r = new Runnable() {
520 public void run() {
521 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
522 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
523
524 // Get the list of workspace screens. We need to append to this list and
525 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
526 // called.
527 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
528 synchronized(sBgLock) {
529 for (ItemInfo item : workspaceApps) {
530 if (item instanceof ShortcutInfo) {
531 // Short-circuit this logic if the icon exists somewhere on the workspace
532 if (shortcutExists(context, item.getIntent(), item.user)) {
533 continue;
534 }
535 }
536
537 // Find appropriate space for the item.
538 Pair<Long, int[]> coords = findSpaceForItem(context,
539 workspaceScreens, addedWorkspaceScreensFinal,
540 1, 1);
541 long screenId = coords.first;
542 int[] cordinates = coords.second;
543
544 ItemInfo itemInfo;
545 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
546 itemInfo = item;
547 } else if (item instanceof AppInfo) {
548 itemInfo = ((AppInfo) item).makeShortcut();
549 } else {
550 throw new RuntimeException("Unexpected info type");
551 }
552
553 // Add the shortcut to the db
554 addItemToDatabase(context, itemInfo,
555 LauncherSettings.Favorites.CONTAINER_DESKTOP,
556 screenId, cordinates[0], cordinates[1]);
557 // Save the ShortcutInfo for binding in the workspace
558 addedShortcutsFinal.add(itemInfo);
559 }
560 }
561
562 // Update the workspace screens
563 updateWorkspaceScreenOrder(context, workspaceScreens);
564
565 if (!addedShortcutsFinal.isEmpty()) {
566 runOnMainThread(new Runnable() {
567 public void run() {
568 Callbacks cb = getCallback();
569 if (callbacks == cb && cb != null) {
570 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
571 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
572 if (!addedShortcutsFinal.isEmpty()) {
573 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
574 long lastScreenId = info.screenId;
575 for (ItemInfo i : addedShortcutsFinal) {
576 if (i.screenId == lastScreenId) {
577 addAnimated.add(i);
578 } else {
579 addNotAnimated.add(i);
580 }
581 }
582 }
583 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
584 addNotAnimated, addAnimated, null);
585 }
586 }
587 });
588 }
589 }
590 };
591 runOnWorkerThread(r);
592 }
593
594 private void unbindItemInfosAndClearQueuedBindRunnables() {
595 if (sWorkerThread.getThreadId() == Process.myTid()) {
596 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
597 "main thread");
598 }
599
600 // Clear any deferred bind runnables
601 synchronized (mDeferredBindRunnables) {
602 mDeferredBindRunnables.clear();
603 }
604
605 // Remove any queued UI runnables
606 mHandler.cancelAll();
607 // Unbind all the workspace items
608 unbindWorkspaceItemsOnMainThread();
609 }
610
611 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
612 void unbindWorkspaceItemsOnMainThread() {
613 // Ensure that we don't use the same workspace items data structure on the main thread
614 // by making a copy of workspace items first.
615 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
616 synchronized (sBgLock) {
617 tmpItems.addAll(sBgWorkspaceItems);
618 tmpItems.addAll(sBgAppWidgets);
619 }
620 Runnable r = new Runnable() {
621 @Override
622 public void run() {
623 for (ItemInfo item : tmpItems) {
624 item.unbind();
625 }
626 }
627 };
628 runOnMainThread(r);
629 }
630
631 /**
632 * Adds an item to the DB if it was not created previously, or move it to a new
633 * <container, screen, cellX, cellY>
634 */
635 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
636 long screenId, int cellX, int cellY) {
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(
647 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
648 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
649 if (modelItem != null && item != modelItem) {
650 // check all the data is consistent
651 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
652 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
653 ShortcutInfo shortcut = (ShortcutInfo) item;
654 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
655 modelShortcut.intent.filterEquals(shortcut.intent) &&
656 modelShortcut.id == shortcut.id &&
657 modelShortcut.itemType == shortcut.itemType &&
658 modelShortcut.container == shortcut.container &&
659 modelShortcut.screenId == shortcut.screenId &&
660 modelShortcut.cellX == shortcut.cellX &&
661 modelShortcut.cellY == shortcut.cellY &&
662 modelShortcut.spanX == shortcut.spanX &&
663 modelShortcut.spanY == shortcut.spanY &&
664 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
665 (modelShortcut.dropPos != null &&
666 shortcut.dropPos != null &&
667 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
668 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
669 // For all intents and purposes, this is the same object
670 return;
671 }
672 }
673
674 // the modelItem needs to match up perfectly with item if our model is
675 // to be consistent with the database-- for now, just require
676 // modelItem == item or the equality check above
677 String msg = "item: " + ((item != null) ? item.toString() : "null") +
678 "modelItem: " +
679 ((modelItem != null) ? modelItem.toString() : "null") +
680 "Error: ItemInfo passed to checkItemInfo doesn't match original";
681 RuntimeException e = new RuntimeException(msg);
682 if (stackTrace != null) {
683 e.setStackTrace(stackTrace);
684 }
685 throw e;
686 }
687 }
688
689 static void checkItemInfo(final ItemInfo item) {
690 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
691 final long itemId = item.id;
692 Runnable r = new Runnable() {
693 public void run() {
694 synchronized (sBgLock) {
695 checkItemInfoLocked(itemId, item, stackTrace);
696 }
697 }
698 };
699 runOnWorkerThread(r);
700 }
701
702 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
703 final ItemInfo item, final String callingFunction) {
704 final long itemId = item.id;
705 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
706 final ContentResolver cr = context.getContentResolver();
707
708 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
709 Runnable r = new Runnable() {
710 public void run() {
711 cr.update(uri, values, null, null);
712 updateItemArrays(item, itemId, stackTrace);
713 }
714 };
715 runOnWorkerThread(r);
716 }
717
718 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
719 final ArrayList<ItemInfo> items, final String callingFunction) {
720 final ContentResolver cr = context.getContentResolver();
721
722 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
723 Runnable r = new Runnable() {
724 public void run() {
725 ArrayList<ContentProviderOperation> ops =
726 new ArrayList<ContentProviderOperation>();
727 int count = items.size();
728 for (int i = 0; i < count; i++) {
729 ItemInfo item = items.get(i);
730 final long itemId = item.id;
731 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
732 ContentValues values = valuesList.get(i);
733
734 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
735 updateItemArrays(item, itemId, stackTrace);
736
737 }
738 try {
739 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
740 } catch (Exception e) {
741 e.printStackTrace();
742 }
743 }
744 };
745 runOnWorkerThread(r);
746 }
747
748 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
749 // Lock on mBgLock *after* the db operation
750 synchronized (sBgLock) {
751 checkItemInfoLocked(itemId, item, stackTrace);
752
753 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
754 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
755 // Item is in a folder, make sure this folder exists
756 if (!sBgFolders.containsKey(item.container)) {
757 // An items container is being set to a that of an item which is not in
758 // the list of Folders.
759 String msg = "item: " + item + " container being set to: " +
760 item.container + ", not in the list of folders";
761 Log.e(TAG, msg);
762 }
763 }
764
765 // Items are added/removed from the corresponding FolderInfo elsewhere, such
766 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
767 // that are on the desktop, as appropriate
768 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
769 if (modelItem != null &&
770 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
771 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
772 switch (modelItem.itemType) {
773 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
774 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
775 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
776 if (!sBgWorkspaceItems.contains(modelItem)) {
777 sBgWorkspaceItems.add(modelItem);
778 }
779 break;
780 default:
781 break;
782 }
783 } else {
784 sBgWorkspaceItems.remove(modelItem);
785 }
786 }
787 }
788
789 /**
790 * Move an item in the DB to a new <container, screen, cellX, cellY>
791 */
792 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
793 final long screenId, final int cellX, final int cellY) {
794 item.container = container;
795 item.cellX = cellX;
796 item.cellY = cellY;
797
798 // We store hotseat items in canonical form which is this orientation invariant position
799 // in the hotseat
800 if (context instanceof Launcher && screenId < 0 &&
801 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
802 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
803 } else {
804 item.screenId = screenId;
805 }
806
807 final ContentValues values = new ContentValues();
808 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
809 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
810 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
811 values.put(LauncherSettings.Favorites.RANK, item.rank);
812 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
813
814 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
815 }
816
817 /**
818 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
819 * cellX, cellY have already been updated on the ItemInfos.
820 */
821 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
822 final long container, final int screen) {
823
824 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
825 int count = items.size();
826
827 for (int i = 0; i < count; i++) {
828 ItemInfo item = items.get(i);
829 item.container = container;
830
831 // We store hotseat items in canonical form which is this orientation invariant position
832 // in the hotseat
833 if (context instanceof Launcher && screen < 0 &&
834 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
835 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
836 item.cellY);
837 } else {
838 item.screenId = screen;
839 }
840
841 final ContentValues values = new ContentValues();
842 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
843 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
844 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
845 values.put(LauncherSettings.Favorites.RANK, item.rank);
846 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
847
848 contentValues.add(values);
849 }
850 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
851 }
852
853 /**
854 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
855 */
856 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
857 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
858 item.container = container;
859 item.cellX = cellX;
860 item.cellY = cellY;
861 item.spanX = spanX;
862 item.spanY = spanY;
863
864 // We store hotseat items in canonical form which is this orientation invariant position
865 // in the hotseat
866 if (context instanceof Launcher && screenId < 0 &&
867 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
868 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
869 } else {
870 item.screenId = screenId;
871 }
872
873 final ContentValues values = new ContentValues();
874 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
875 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
876 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
877 values.put(LauncherSettings.Favorites.RANK, item.rank);
878 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
879 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
880 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
881
882 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
883 }
884
885 /**
886 * Update an item to the database in a specified container.
887 */
888 public static void updateItemInDatabase(Context context, final ItemInfo item) {
889 final ContentValues values = new ContentValues();
890 item.onAddToDatabase(context, values);
891 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
892 }
893
894 private void assertWorkspaceLoaded() {
895 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
896 throw new RuntimeException("Trying to add shortcut while loader is running");
897 }
898 }
899
900 /**
901 * Returns true if the shortcuts already exists on the workspace. This must be called after
902 * the workspace has been loaded. We identify a shortcut by its intent.
903 */
904 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
905 assertWorkspaceLoaded();
906 final String intentWithPkg, intentWithoutPkg;
907 if (intent.getComponent() != null) {
908 // If component is not null, an intent with null package will produce
909 // the same result and should also be a match.
910 String packageName = intent.getComponent().getPackageName();
911 if (intent.getPackage() != null) {
912 intentWithPkg = intent.toUri(0);
913 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
914 } else {
915 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
916 intentWithoutPkg = intent.toUri(0);
917 }
918 } else {
919 intentWithPkg = intent.toUri(0);
920 intentWithoutPkg = intent.toUri(0);
921 }
922
923 synchronized (sBgLock) {
924 for (ItemInfo item : sBgItemsIdMap) {
925 if (item instanceof ShortcutInfo) {
926 ShortcutInfo info = (ShortcutInfo) item;
927 Intent targetIntent = info.promisedIntent == null
928 ? info.intent : info.promisedIntent;
929 if (targetIntent != null && info.user.equals(user)) {
930 String s = targetIntent.toUri(0);
931 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
932 return true;
933 }
934 }
935 }
936 }
937 }
938 return false;
939 }
940
941 /**
942 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
943 */
944 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
945 final ContentResolver cr = context.getContentResolver();
946 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
947 "_id=? and (itemType=? or itemType=?)",
948 new String[] { String.valueOf(id),
949 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
950
951 try {
952 if (c.moveToFirst()) {
953 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
954 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
955 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
956 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
957 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
958 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
959 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
960
961 FolderInfo folderInfo = null;
962 switch (c.getInt(itemTypeIndex)) {
963 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
964 folderInfo = findOrMakeFolder(folderList, id);
965 break;
966 }
967
968 // Do not trim the folder label, as is was set by the user.
969 folderInfo.title = c.getString(titleIndex);
970 folderInfo.id = id;
971 folderInfo.container = c.getInt(containerIndex);
972 folderInfo.screenId = c.getInt(screenIndex);
973 folderInfo.cellX = c.getInt(cellXIndex);
974 folderInfo.cellY = c.getInt(cellYIndex);
975 folderInfo.options = c.getInt(optionsIndex);
976
977 return folderInfo;
978 }
979 } finally {
980 c.close();
981 }
982
983 return null;
984 }
985
986 /**
987 * Add an item to the database in a specified container. Sets the container, screen, cellX and
988 * cellY fields of the item. Also assigns an ID to the item.
989 */
990 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
991 final long screenId, final int cellX, final int cellY) {
992 item.container = container;
993 item.cellX = cellX;
994 item.cellY = cellY;
995 // We store hotseat items in canonical form which is this orientation invariant position
996 // in the hotseat
997 if (context instanceof Launcher && screenId < 0 &&
998 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
999 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1000 } else {
1001 item.screenId = screenId;
1002 }
1003
1004 final ContentValues values = new ContentValues();
1005 final ContentResolver cr = context.getContentResolver();
1006 item.onAddToDatabase(context, values);
1007
1008 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1009 values.put(LauncherSettings.Favorites._ID, item.id);
1010
1011 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1012 Runnable r = new Runnable() {
1013 public void run() {
1014 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
1015
1016 // Lock on mBgLock *after* the db operation
1017 synchronized (sBgLock) {
1018 checkItemInfoLocked(item.id, item, stackTrace);
1019 sBgItemsIdMap.put(item.id, item);
1020 switch (item.itemType) {
1021 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1022 sBgFolders.put(item.id, (FolderInfo) item);
1023 // Fall through
1024 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1025 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1026 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1027 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1028 sBgWorkspaceItems.add(item);
1029 } else {
1030 if (!sBgFolders.containsKey(item.container)) {
1031 // Adding an item to a folder that doesn't exist.
1032 String msg = "adding item: " + item + " to a folder that " +
1033 " doesn't exist";
1034 Log.e(TAG, msg);
1035 }
1036 }
1037 break;
1038 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1039 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1040 break;
1041 }
1042 }
1043 }
1044 };
1045 runOnWorkerThread(r);
1046 }
1047
1048 /**
1049 * Creates a new unique child id, for a given cell span across all layouts.
1050 */
1051 static int getCellLayoutChildId(
1052 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1053 return (((int) container & 0xFF) << 24)
1054 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1055 }
1056
1057 private static ArrayList<ItemInfo> getItemsByPackageName(
1058 final String pn, final UserHandleCompat user) {
1059 ItemInfoFilter filter = new ItemInfoFilter() {
1060 @Override
1061 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1062 return cn.getPackageName().equals(pn) && info.user.equals(user);
1063 }
1064 };
1065 return filterItemInfos(sBgItemsIdMap, filter);
1066 }
1067
1068 /**
1069 * Removes all the items from the database corresponding to the specified package.
1070 */
1071 static void deletePackageFromDatabase(Context context, final String pn,
1072 final UserHandleCompat user) {
1073 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1074 }
1075
1076 /**
1077 * Removes the specified item from the database
1078 * @param context
1079 * @param item
1080 */
1081 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1082 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1083 items.add(item);
1084 deleteItemsFromDatabase(context, items);
1085 }
1086
1087 /**
1088 * Removes the specified items from the database
1089 * @param context
1090 * @param item
1091 */
1092 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1093 final ContentResolver cr = context.getContentResolver();
1094 Runnable r = new Runnable() {
1095 public void run() {
1096 for (ItemInfo item : items) {
1097 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1098 cr.delete(uri, null, null);
1099
1100 // Lock on mBgLock *after* the db operation
1101 synchronized (sBgLock) {
1102 switch (item.itemType) {
1103 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1104 sBgFolders.remove(item.id);
1105 for (ItemInfo info: sBgItemsIdMap) {
1106 if (info.container == item.id) {
1107 // We are deleting a folder which still contains items that
1108 // think they are contained by that folder.
1109 String msg = "deleting a folder (" + item + ") which still " +
1110 "contains items (" + info + ")";
1111 Log.e(TAG, msg);
1112 }
1113 }
1114 sBgWorkspaceItems.remove(item);
1115 break;
1116 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1117 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1118 sBgWorkspaceItems.remove(item);
1119 break;
1120 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1121 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1122 break;
1123 }
1124 sBgItemsIdMap.remove(item.id);
1125 }
1126 }
1127 }
1128 };
1129 runOnWorkerThread(r);
1130 }
1131
1132 /**
1133 * Update the order of the workspace screens in the database. The array list contains
1134 * a list of screen ids in the order that they should appear.
1135 */
1136 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1137 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1138 final ContentResolver cr = context.getContentResolver();
1139 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1140
1141 // Remove any negative screen ids -- these aren't persisted
1142 Iterator<Long> iter = screensCopy.iterator();
1143 while (iter.hasNext()) {
1144 long id = iter.next();
1145 if (id < 0) {
1146 iter.remove();
1147 }
1148 }
1149
1150 Runnable r = new Runnable() {
1151 @Override
1152 public void run() {
1153 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1154 // Clear the table
1155 ops.add(ContentProviderOperation.newDelete(uri).build());
1156 int count = screensCopy.size();
1157 for (int i = 0; i < count; i++) {
1158 ContentValues v = new ContentValues();
1159 long screenId = screensCopy.get(i);
1160 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1161 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1162 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1163 }
1164
1165 try {
1166 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1167 } catch (Exception ex) {
1168 throw new RuntimeException(ex);
1169 }
1170
1171 synchronized (sBgLock) {
1172 sBgWorkspaceScreens.clear();
1173 sBgWorkspaceScreens.addAll(screensCopy);
1174 }
1175 }
1176 };
1177 runOnWorkerThread(r);
1178 }
1179
1180 /**
1181 * Remove the contents of the specified folder from the database
1182 */
1183 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1184 final ContentResolver cr = context.getContentResolver();
1185
1186 Runnable r = new Runnable() {
1187 public void run() {
1188 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1189 // Lock on mBgLock *after* the db operation
1190 synchronized (sBgLock) {
1191 sBgItemsIdMap.remove(info.id);
1192 sBgFolders.remove(info.id);
1193 sBgWorkspaceItems.remove(info);
1194 }
1195
1196 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1197 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 for (ItemInfo childInfo : info.contents) {
1201 sBgItemsIdMap.remove(childInfo.id);
1202 }
1203 }
1204 }
1205 };
1206 runOnWorkerThread(r);
1207 }
1208
1209 /**
1210 * Set this as the current Launcher activity object for the loader.
1211 */
1212 public void initialize(Callbacks callbacks) {
1213 synchronized (mLock) {
1214 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1215 // workspace to prevent leaking Launcher activities on orientation change.
1216 unbindItemInfosAndClearQueuedBindRunnables();
1217 mCallbacks = new WeakReference<Callbacks>(callbacks);
1218 }
1219 }
1220
1221 @Override
1222 public void onPackageChanged(String packageName, UserHandleCompat user) {
1223 int op = PackageUpdatedTask.OP_UPDATE;
1224 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1225 user));
1226 }
1227
1228 @Override
1229 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1230 int op = PackageUpdatedTask.OP_REMOVE;
1231 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1232 user));
1233 }
1234
1235 @Override
1236 public void onPackageAdded(String packageName, UserHandleCompat user) {
1237 int op = PackageUpdatedTask.OP_ADD;
1238 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1239 user));
1240 }
1241
1242 @Override
1243 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1244 boolean replacing) {
1245 if (!replacing) {
1246 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1247 user));
1248 if (mAppsCanBeOnRemoveableStorage) {
1249 // Only rebind if we support removable storage. It catches the
1250 // case where
1251 // apps on the external sd card need to be reloaded
1252 startLoaderFromBackground();
1253 }
1254 } else {
1255 // If we are replacing then just update the packages in the list
1256 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1257 packageNames, user));
1258 }
1259 }
1260
1261 @Override
1262 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1263 boolean replacing) {
1264 if (!replacing) {
1265 enqueuePackageUpdated(new PackageUpdatedTask(
1266 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1267 user));
1268 }
1269 }
1270
1271 /**
1272 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1273 * ACTION_PACKAGE_CHANGED.
1274 */
1275 @Override
1276 public void onReceive(Context context, Intent intent) {
1277 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1278
1279 final String action = intent.getAction();
1280 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1281 // If we have changed locale we need to clear out the labels in all apps/workspace.
1282 forceReload();
1283 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1284 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1285 Callbacks callbacks = getCallback();
1286 if (callbacks != null) {
1287 callbacks.bindSearchablesChanged();
1288 }
1289 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1290 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1291 forceReload();
1292 }
1293 }
1294
1295 void forceReload() {
1296 resetLoadedState(true, true);
1297
1298 // Do this here because if the launcher activity is running it will be restarted.
1299 // If it's not running startLoaderFromBackground will merely tell it that it needs
1300 // to reload.
1301 startLoaderFromBackground();
1302 }
1303
1304 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1305 synchronized (mLock) {
1306 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1307 // mWorkspaceLoaded to true later
1308 stopLoaderLocked();
1309 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1310 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1311 }
1312 }
1313
1314 /**
1315 * When the launcher is in the background, it's possible for it to miss paired
1316 * configuration changes. So whenever we trigger the loader from the background
1317 * tell the launcher that it needs to re-run the loader when it comes back instead
1318 * of doing it now.
1319 */
1320 public void startLoaderFromBackground() {
1321 boolean runLoader = false;
1322 Callbacks callbacks = getCallback();
1323 if (callbacks != null) {
1324 // Only actually run the loader if they're not paused.
1325 if (!callbacks.setLoadOnResume()) {
1326 runLoader = true;
1327 }
1328 }
1329 if (runLoader) {
1330 startLoader(PagedView.INVALID_RESTORE_PAGE);
1331 }
1332 }
1333
1334 /**
1335 * If there is already a loader task running, tell it to stop.
1336 */
1337 private void stopLoaderLocked() {
1338 LoaderTask oldTask = mLoaderTask;
1339 if (oldTask != null) {
1340 oldTask.stopLocked();
1341 }
1342 }
1343
1344 public boolean isCurrentCallbacks(Callbacks callbacks) {
1345 return (mCallbacks != null && mCallbacks.get() == callbacks);
1346 }
1347
1348 public void startLoader(int synchronousBindPage) {
1349 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1350 }
1351
1352 public void startLoader(int synchronousBindPage, int loadFlags) {
1353 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1354 InstallShortcutReceiver.enableInstallQueue();
1355 synchronized (mLock) {
1356 // Clear any deferred bind-runnables from the synchronized load process
1357 // We must do this before any loading/binding is scheduled below.
1358 synchronized (mDeferredBindRunnables) {
1359 mDeferredBindRunnables.clear();
1360 }
1361
1362 // Don't bother to start the thread if we know it's not going to do anything
1363 if (mCallbacks != null && mCallbacks.get() != null) {
1364 // If there is already one running, tell it to stop.
1365 stopLoaderLocked();
1366 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1367 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1368 && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
1369 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1370 } else {
1371 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1372 sWorker.post(mLoaderTask);
1373 }
1374 }
1375 }
1376 }
1377
1378 void bindRemainingSynchronousPages() {
1379 // Post the remaining side pages to be loaded
1380 if (!mDeferredBindRunnables.isEmpty()) {
1381 Runnable[] deferredBindRunnables = null;
1382 synchronized (mDeferredBindRunnables) {
1383 deferredBindRunnables = mDeferredBindRunnables.toArray(
1384 new Runnable[mDeferredBindRunnables.size()]);
1385 mDeferredBindRunnables.clear();
1386 }
1387 for (final Runnable r : deferredBindRunnables) {
1388 mHandler.post(r);
1389 }
1390 }
1391
1392 // Run all the bind complete runnables after workspace is bound.
1393 if (!mBindCompleteRunnables.isEmpty()) {
1394 synchronized (mBindCompleteRunnables) {
1395 for (final Runnable r : mBindCompleteRunnables) {
1396 runOnWorkerThread(r);
1397 }
1398 mBindCompleteRunnables.clear();
1399 }
1400 }
1401 }
1402
1403 public void stopLoader() {
1404 synchronized (mLock) {
1405 if (mLoaderTask != null) {
1406 mLoaderTask.stopLocked();
1407 }
1408 }
1409 }
1410
1411 /**
1412 * Loads the workspace screen ids in an ordered list.
1413 */
1414 @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1415 final ContentResolver contentResolver = context.getContentResolver();
1416 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1417
1418 // Get screens ordered by rank.
1419 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1420 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1421 ArrayList<Long> screenIds = new ArrayList<Long>();
1422 try {
1423 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1424 while (sc.moveToNext()) {
1425 try {
1426 screenIds.add(sc.getLong(idIndex));
1427 } catch (Exception e) {
1428 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1429 + " - invalid screens: " + e, true);
1430 }
1431 }
1432 } finally {
1433 sc.close();
1434 }
1435 return screenIds;
1436 }
1437
1438 public boolean isAllAppsLoaded() {
1439 return mAllAppsLoaded;
1440 }
1441
1442 boolean isLoadingWorkspace() {
1443 synchronized (mLock) {
1444 if (mLoaderTask != null) {
1445 return mLoaderTask.isLoadingWorkspace();
1446 }
1447 }
1448 return false;
1449 }
1450
1451 /**
1452 * Runnable for the thread that loads the contents of the launcher:
1453 * - workspace icons
1454 * - widgets
1455 * - all apps icons
1456 */
1457 private class LoaderTask implements Runnable {
1458 private Context mContext;
1459 @Thunk boolean mIsLoadingAndBindingWorkspace;
1460 private boolean mStopped;
1461 @Thunk boolean mLoadAndBindStepFinished;
1462 private int mFlags;
1463
1464 LoaderTask(Context context, int flags) {
1465 mContext = context;
1466 mFlags = flags;
1467 }
1468
1469 boolean isLoadingWorkspace() {
1470 return mIsLoadingAndBindingWorkspace;
1471 }
1472
1473 private void loadAndBindWorkspace() {
1474 mIsLoadingAndBindingWorkspace = true;
1475
1476 // Load the workspace
1477 if (DEBUG_LOADERS) {
1478 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1479 }
1480
1481 if (!mWorkspaceLoaded) {
1482 loadWorkspace();
1483 synchronized (LoaderTask.this) {
1484 if (mStopped) {
1485 return;
1486 }
1487 mWorkspaceLoaded = true;
1488 }
1489 }
1490
1491 // Bind the workspace
1492 bindWorkspace(-1);
1493 }
1494
1495 private void waitForIdle() {
1496 // Wait until the either we're stopped or the other threads are done.
1497 // This way we don't start loading all apps until the workspace has settled
1498 // down.
1499 synchronized (LoaderTask.this) {
1500 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1501
1502 mHandler.postIdle(new Runnable() {
1503 public void run() {
1504 synchronized (LoaderTask.this) {
1505 mLoadAndBindStepFinished = true;
1506 if (DEBUG_LOADERS) {
1507 Log.d(TAG, "done with previous binding step");
1508 }
1509 LoaderTask.this.notify();
1510 }
1511 }
1512 });
1513
1514 while (!mStopped && !mLoadAndBindStepFinished) {
1515 try {
1516 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1517 // wait no longer than 1sec at a time
1518 this.wait(1000);
1519 } catch (InterruptedException ex) {
1520 // Ignore
1521 }
1522 }
1523 if (DEBUG_LOADERS) {
1524 Log.d(TAG, "waited "
1525 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1526 + "ms for previous step to finish binding");
1527 }
1528 }
1529 }
1530
1531 void runBindSynchronousPage(int synchronousBindPage) {
1532 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1533 // Ensure that we have a valid page index to load synchronously
1534 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1535 "valid page index");
1536 }
1537 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1538 // Ensure that we don't try and bind a specified page when the pages have not been
1539 // loaded already (we should load everything asynchronously in that case)
1540 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1541 }
1542 synchronized (mLock) {
1543 if (mIsLoaderTaskRunning) {
1544 // Ensure that we are never running the background loading at this point since
1545 // we also touch the background collections
1546 throw new RuntimeException("Error! Background loading is already running");
1547 }
1548 }
1549
1550 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1551 // data structures, we can't allow any other thread to touch that data, but because
1552 // this call is synchronous, we can get away with not locking).
1553
1554 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1555 // operations from the previous activity. We need to ensure that all queued operations
1556 // are executed before any synchronous binding work is done.
1557 mHandler.flush();
1558
1559 // Divide the set of loaded items into those that we are binding synchronously, and
1560 // everything else that is to be bound normally (asynchronously).
1561 bindWorkspace(synchronousBindPage);
1562 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1563 // arise from that.
1564 onlyBindAllApps();
1565 }
1566
1567 public void run() {
1568 synchronized (mLock) {
1569 if (mStopped) {
1570 return;
1571 }
1572 mIsLoaderTaskRunning = true;
1573 }
1574 // Optimize for end-user experience: if the Launcher is up and // running with the
1575 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1576 // workspace first (default).
1577 keep_running: {
1578 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1579 loadAndBindWorkspace();
1580
1581 if (mStopped) {
1582 break keep_running;
1583 }
1584
1585 waitForIdle();
1586
1587 // second step
1588 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1589 loadAndBindAllApps();
1590 }
1591
1592 // Clear out this reference, otherwise we end up holding it until all of the
1593 // callback runnables are done.
1594 mContext = null;
1595
1596 synchronized (mLock) {
1597 // If we are still the last one to be scheduled, remove ourselves.
1598 if (mLoaderTask == this) {
1599 mLoaderTask = null;
1600 }
1601 mIsLoaderTaskRunning = false;
1602 mHasLoaderCompletedOnce = true;
1603 }
1604 }
1605
1606 public void stopLocked() {
1607 synchronized (LoaderTask.this) {
1608 mStopped = true;
1609 this.notify();
1610 }
1611 }
1612
1613 /**
1614 * Gets the callbacks object. If we've been stopped, or if the launcher object
1615 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1616 * object that was around when the deferred message was scheduled, and if there's
1617 * a new Callbacks object around then also return null. This will save us from
1618 * calling onto it with data that will be ignored.
1619 */
1620 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1621 synchronized (mLock) {
1622 if (mStopped) {
1623 return null;
1624 }
1625
1626 if (mCallbacks == null) {
1627 return null;
1628 }
1629
1630 final Callbacks callbacks = mCallbacks.get();
1631 if (callbacks != oldCallbacks) {
1632 return null;
1633 }
1634 if (callbacks == null) {
1635 Log.w(TAG, "no mCallbacks");
1636 return null;
1637 }
1638
1639 return callbacks;
1640 }
1641 }
1642
1643 // check & update map of what's occupied; used to discard overlapping/invalid items
1644 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1645 LauncherAppState app = LauncherAppState.getInstance();
1646 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1647 final int countX = (int) profile.numColumns;
1648 final int countY = (int) profile.numRows;
1649
1650 long containerIndex = item.screenId;
1651 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1652 // Return early if we detect that an item is under the hotseat button
1653 if (mCallbacks == null ||
1654 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1655 Log.e(TAG, "Error loading shortcut into hotseat " + item
1656 + " into position (" + item.screenId + ":" + item.cellX + ","
1657 + item.cellY + ") occupied by all apps");
1658 return false;
1659 }
1660
1661 final ItemInfo[][] hotseatItems =
1662 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1663
1664 if (item.screenId >= profile.numHotseatIcons) {
1665 Log.e(TAG, "Error loading shortcut " + item
1666 + " into hotseat position " + item.screenId
1667 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1668 + ")");
1669 return false;
1670 }
1671
1672 if (hotseatItems != null) {
1673 if (hotseatItems[(int) item.screenId][0] != null) {
1674 Log.e(TAG, "Error loading shortcut into hotseat " + item
1675 + " into position (" + item.screenId + ":" + item.cellX + ","
1676 + item.cellY + ") occupied by "
1677 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1678 [(int) item.screenId][0]);
1679 return false;
1680 } else {
1681 hotseatItems[(int) item.screenId][0] = item;
1682 return true;
1683 }
1684 } else {
1685 final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
1686 items[(int) item.screenId][0] = item;
1687 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1688 return true;
1689 }
1690 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1691 // Skip further checking if it is not the hotseat or workspace container
1692 return true;
1693 }
1694
1695 if (!occupied.containsKey(item.screenId)) {
1696 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1697 occupied.put(item.screenId, items);
1698 }
1699
1700 final ItemInfo[][] screens = occupied.get(item.screenId);
1701 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1702 item.cellX < 0 || item.cellY < 0 ||
1703 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1704 Log.e(TAG, "Error loading shortcut " + item
1705 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1706 + item.cellX + "," + item.cellY
1707 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1708 return false;
1709 }
1710
1711 // Check if any workspace icons overlap with each other
1712 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1713 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1714 if (screens[x][y] != null) {
1715 Log.e(TAG, "Error loading shortcut " + item
1716 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1717 + x + "," + y
1718 + ") occupied by "
1719 + screens[x][y]);
1720 return false;
1721 }
1722 }
1723 }
1724 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1725 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1726 screens[x][y] = item;
1727 }
1728 }
1729
1730 return true;
1731 }
1732
1733 /** Clears all the sBg data structures */
1734 private void clearSBgDataStructures() {
1735 synchronized (sBgLock) {
1736 sBgWorkspaceItems.clear();
1737 sBgAppWidgets.clear();
1738 sBgFolders.clear();
1739 sBgItemsIdMap.clear();
1740 sBgWorkspaceScreens.clear();
1741 }
1742 }
1743
1744 private void loadWorkspace() {
1745 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1746
1747 final Context context = mContext;
1748 final ContentResolver contentResolver = context.getContentResolver();
1749 final PackageManager manager = context.getPackageManager();
1750 final boolean isSafeMode = manager.isSafeMode();
1751 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1752 final boolean isSdCardReady = context.registerReceiver(null,
1753 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1754
1755 LauncherAppState app = LauncherAppState.getInstance();
1756 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1757 int countX = (int) profile.numColumns;
1758 int countY = (int) profile.numRows;
1759
1760 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1761 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1762 LauncherAppState.getLauncherProvider().deleteDatabase();
1763 }
1764
1765 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1766 // append the user's Launcher2 shortcuts
1767 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1768 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1769 } else {
1770 // Make sure the default workspace is loaded
1771 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1772 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1773 }
1774
1775 synchronized (sBgLock) {
1776 clearSBgDataStructures();
1777 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1778 .getInstance(mContext).updateAndGetActiveSessionCache();
1779
1780 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1781 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1782 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1783 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1784 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1785
1786 // +1 for the hotseat (it can be larger than the workspace)
1787 // Load workspace in reverse order to ensure that latest items are loaded first (and
1788 // before any earlier duplicates)
1789 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1790
1791 try {
1792 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1793 final int intentIndex = c.getColumnIndexOrThrow
1794 (LauncherSettings.Favorites.INTENT);
1795 final int titleIndex = c.getColumnIndexOrThrow
1796 (LauncherSettings.Favorites.TITLE);
1797 final int containerIndex = c.getColumnIndexOrThrow(
1798 LauncherSettings.Favorites.CONTAINER);
1799 final int itemTypeIndex = c.getColumnIndexOrThrow(
1800 LauncherSettings.Favorites.ITEM_TYPE);
1801 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1802 LauncherSettings.Favorites.APPWIDGET_ID);
1803 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1804 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1805 final int screenIndex = c.getColumnIndexOrThrow(
1806 LauncherSettings.Favorites.SCREEN);
1807 final int cellXIndex = c.getColumnIndexOrThrow
1808 (LauncherSettings.Favorites.CELLX);
1809 final int cellYIndex = c.getColumnIndexOrThrow
1810 (LauncherSettings.Favorites.CELLY);
1811 final int spanXIndex = c.getColumnIndexOrThrow
1812 (LauncherSettings.Favorites.SPANX);
1813 final int spanYIndex = c.getColumnIndexOrThrow(
1814 LauncherSettings.Favorites.SPANY);
1815 final int rankIndex = c.getColumnIndexOrThrow(
1816 LauncherSettings.Favorites.RANK);
1817 final int restoredIndex = c.getColumnIndexOrThrow(
1818 LauncherSettings.Favorites.RESTORED);
1819 final int profileIdIndex = c.getColumnIndexOrThrow(
1820 LauncherSettings.Favorites.PROFILE_ID);
1821 final int optionsIndex = c.getColumnIndexOrThrow(
1822 LauncherSettings.Favorites.OPTIONS);
1823 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1824
1825 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1826 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1827 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1828 }
1829
1830 ShortcutInfo info;
1831 String intentDescription;
1832 LauncherAppWidgetInfo appWidgetInfo;
1833 int container;
1834 long id;
1835 long serialNumber;
1836 Intent intent;
1837 UserHandleCompat user;
1838
1839 while (!mStopped && c.moveToNext()) {
1840 try {
1841 int itemType = c.getInt(itemTypeIndex);
1842 boolean restored = 0 != c.getInt(restoredIndex);
1843 boolean allowMissingTarget = false;
1844 container = c.getInt(containerIndex);
1845
1846 switch (itemType) {
1847 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1848 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1849 id = c.getLong(idIndex);
1850 intentDescription = c.getString(intentIndex);
1851 serialNumber = c.getInt(profileIdIndex);
1852 user = allUsers.get(serialNumber);
1853 int promiseType = c.getInt(restoredIndex);
1854 int disabledState = 0;
1855 boolean itemReplaced = false;
1856 if (user == null) {
1857 // User has been deleted remove the item.
1858 itemsToRemove.add(id);
1859 continue;
1860 }
1861 try {
1862 intent = Intent.parseUri(intentDescription, 0);
1863 ComponentName cn = intent.getComponent();
1864 if (cn != null && cn.getPackageName() != null) {
1865 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1866 cn.getPackageName(), user);
1867 boolean validComponent = validPkg &&
1868 launcherApps.isActivityEnabledForProfile(cn, user);
1869
1870 if (validComponent) {
1871 if (restored) {
1872 // no special handling necessary for this item
1873 restoredRows.add(id);
1874 restored = false;
1875 }
1876 } else if (validPkg) {
1877 intent = null;
1878 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1879 // We allow auto install apps to have their intent
1880 // updated after an install.
1881 intent = manager.getLaunchIntentForPackage(
1882 cn.getPackageName());
1883 if (intent != null) {
1884 ContentValues values = new ContentValues();
1885 values.put(LauncherSettings.Favorites.INTENT,
1886 intent.toUri(0));
1887 updateItem(id, values);
1888 }
1889 }
1890
1891 if (intent == null) {
1892 // The app is installed but the component is no
1893 // longer available.
1894 Launcher.addDumpLog(TAG,
1895 "Invalid component removed: " + cn, true);
1896 itemsToRemove.add(id);
1897 continue;
1898 } else {
1899 // no special handling necessary for this item
1900 restoredRows.add(id);
1901 restored = false;
1902 }
1903 } else if (restored) {
1904 // Package is not yet available but might be
1905 // installed later.
1906 Launcher.addDumpLog(TAG,
1907 "package not yet restored: " + cn, true);
1908
1909 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1910 // Restore has started once.
1911 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1912 // App restore has started. Update the flag
1913 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1914 ContentValues values = new ContentValues();
1915 values.put(LauncherSettings.Favorites.RESTORED,
1916 promiseType);
1917 updateItem(id, values);
1918 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1919 // This is a common app. Try to replace this.
1920 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1921 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1922 if (parser.findDefaultApp()) {
1923 // Default app found. Replace it.
1924 intent = parser.parsedIntent;
1925 cn = intent.getComponent();
1926 ContentValues values = parser.parsedValues;
1927 values.put(LauncherSettings.Favorites.RESTORED, 0);
1928 updateItem(id, values);
1929 restored = false;
1930 itemReplaced = true;
1931
1932 } else if (REMOVE_UNRESTORED_ICONS) {
1933 Launcher.addDumpLog(TAG,
1934 "Unrestored package removed: " + cn, true);
1935 itemsToRemove.add(id);
1936 continue;
1937 }
1938 } else if (REMOVE_UNRESTORED_ICONS) {
1939 Launcher.addDumpLog(TAG,
1940 "Unrestored package removed: " + cn, true);
1941 itemsToRemove.add(id);
1942 continue;
1943 }
1944 } else if (launcherApps.isAppEnabled(
1945 manager, cn.getPackageName(),
1946 PackageManager.GET_UNINSTALLED_PACKAGES)) {
1947 // Package is present but not available.
1948 allowMissingTarget = true;
1949 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1950 } else if (!isSdCardReady) {
1951 // SdCard is not ready yet. Package might get available,
1952 // once it is ready.
1953 Launcher.addDumpLog(TAG, "Invalid package: " + cn
1954 + " (check again later)", true);
1955 HashSet<String> pkgs = sPendingPackages.get(user);
1956 if (pkgs == null) {
1957 pkgs = new HashSet<String>();
1958 sPendingPackages.put(user, pkgs);
1959 }
1960 pkgs.add(cn.getPackageName());
1961 allowMissingTarget = true;
1962 // Add the icon on the workspace anyway.
1963
1964 } else {
1965 // Do not wait for external media load anymore.
1966 // Log the invalid package, and remove it
1967 Launcher.addDumpLog(TAG,
1968 "Invalid package removed: " + cn, true);
1969 itemsToRemove.add(id);
1970 continue;
1971 }
1972 } else if (cn == null) {
1973 // For shortcuts with no component, keep them as they are
1974 restoredRows.add(id);
1975 restored = false;
1976 }
1977 } catch (URISyntaxException e) {
1978 Launcher.addDumpLog(TAG,
1979 "Invalid uri: " + intentDescription, true);
1980 continue;
1981 }
1982
1983 boolean useLowResIcon = container >= 0 &&
1984 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1985
1986 if (itemReplaced) {
1987 if (user.equals(UserHandleCompat.myUserHandle())) {
1988 info = getAppShortcutInfo(manager, intent, user, context, null,
1989 cursorIconInfo.iconIndex, titleIndex,
1990 false, useLowResIcon);
1991 } else {
1992 // Don't replace items for other profiles.
1993 itemsToRemove.add(id);
1994 continue;
1995 }
1996 } else if (restored) {
1997 if (user.equals(UserHandleCompat.myUserHandle())) {
1998 Launcher.addDumpLog(TAG,
1999 "constructing info for partially restored package",
2000 true);
2001 info = getRestoredItemInfo(c, titleIndex, intent,
2002 promiseType, itemType, cursorIconInfo, context);
2003 intent = getRestoredItemIntent(c, context, intent);
2004 } else {
2005 // Don't restore items for other profiles.
2006 itemsToRemove.add(id);
2007 continue;
2008 }
2009 } else if (itemType ==
2010 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2011 info = getAppShortcutInfo(manager, intent, user, context, c,
2012 cursorIconInfo.iconIndex, titleIndex,
2013 allowMissingTarget, useLowResIcon);
2014 } else {
2015 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
2016
2017 // App shortcuts that used to be automatically added to Launcher
2018 // didn't always have the correct intent flags set, so do that
2019 // here
2020 if (intent.getAction() != null &&
2021 intent.getCategories() != null &&
2022 intent.getAction().equals(Intent.ACTION_MAIN) &&
2023 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2024 intent.addFlags(
2025 Intent.FLAG_ACTIVITY_NEW_TASK |
2026 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2027 }
2028 }
2029
2030 if (info != null) {
2031 info.id = id;
2032 info.intent = intent;
2033 info.container = container;
2034 info.screenId = c.getInt(screenIndex);
2035 info.cellX = c.getInt(cellXIndex);
2036 info.cellY = c.getInt(cellYIndex);
2037 info.rank = c.getInt(rankIndex);
2038 info.spanX = 1;
2039 info.spanY = 1;
2040 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2041 if (info.promisedIntent != null) {
2042 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber🔵
2043 }
2044 info.isDisabled = disabledState;
2045 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2046 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2047 }
2048
2049 // check & update map of what's occupied
2050 if (!checkItemPlacement(occupied, info)) {
2051 itemsToRemove.add(id);
2052 break;
2053 }
2054
2055 if (restored) {
2056 ComponentName cn = info.getTargetComponent();
2057 if (cn != null) {
2058 Integer progress = installingPkgs.get(cn.getPackageName());
2059 if (progress != null) {
2060 info.setInstallProgress(progress);
2061 } else {
2062 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2063 }
2064 }
2065 }
2066
2067 switch (container) {
2068 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2069 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2070 sBgWorkspaceItems.add(info);
2071 break;
2072 default:
2073 // Item is in a user folder
2074 FolderInfo folderInfo =
2075 findOrMakeFolder(sBgFolders, container);
2076 folderInfo.add(info);
2077 break;
2078 }
2079 sBgItemsIdMap.put(info.id, info);
2080 } else {
2081 throw new RuntimeException("Unexpected null ShortcutInfo");
2082 }
2083 break;
2084
2085 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2086 id = c.getLong(idIndex);
2087 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2088
2089 // Do not trim the folder label, as is was set by the user.
2090 folderInfo.title = c.getString(titleIndex);
2091 folderInfo.id = id;
2092 folderInfo.container = container;
2093 folderInfo.screenId = c.getInt(screenIndex);
2094 folderInfo.cellX = c.getInt(cellXIndex);
2095 folderInfo.cellY = c.getInt(cellYIndex);
2096 folderInfo.spanX = 1;
2097 folderInfo.spanY = 1;
2098 folderInfo.options = c.getInt(optionsIndex);
2099
2100 // check & update map of what's occupied
2101 if (!checkItemPlacement(occupied, folderInfo)) {
2102 itemsToRemove.add(id);
2103 break;
2104 }
2105
2106 switch (container) {
2107 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2108 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2109 sBgWorkspaceItems.add(folderInfo);
2110 break;
2111 }
2112
2113 if (restored) {
2114 // no special handling required for restored folders
2115 restoredRows.add(id);
2116 }
2117
2118 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2119 sBgFolders.put(folderInfo.id, folderInfo);
2120 break;
2121
2122 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2123 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2124 // Read all Launcher-specific widget details
2125 boolean customWidget = itemType ==
2126 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2127
2128 int appWidgetId = c.getInt(appWidgetIdIndex);
2129 <<<<<<< GitAnalyzerPlus_ours
2130 serialNumber = c.getLong(profileIdIndex);
2131 ||||||| GitAnalyzerPlus_base
2132 if (info != null) {
2133 info.id = id;
2134 info.intent = intent;
2135 container = c.getInt(containerIndex);
2136 info.container = container;
2137 info.screenId = c.getInt(screenIndex);
2138 info.cellX = c.getInt(cellXIndex);
2139 info.cellY = c.getInt(cellYIndex);
2140 info.rank = c.getInt(rankIndex);
2141 info.spanX = 1;
2142 info.spanY = 1;
2143 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2144 info.isDisabled = disabledState;
2145 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2146 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2147 }
2148
2149 // check & update map of what's occupied
2150 if (!checkItemPlacement(occupied, info)) {
2151 itemsToRemove.add(id);
2152 break;
2153 }
2154
2155 switch (container) {
2156 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2157 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2158 sBgWorkspaceItems.add(info);
2159 break;
2160 default:
2161 // Item is in a user folder
2162 FolderInfo folderInfo =
2163 findOrMakeFolder(sBgFolders, container);
2164 folderInfo.add(info);
2165 break;
2166 }
2167 sBgItemsIdMap.put(info.id, info);
2168
2169 // now that we've loaded everthing re-save it with the
2170 // icon in case it disappears somehow.
2171 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
2172 } else {
2173 throw new RuntimeException("Unexpected null ShortcutInfo");
2174 }
2175 break;
2176
2177 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2178 id = c.getLong(idIndex);
2179 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2180
2181 folderInfo.title = c.getString(titleIndex);
2182 folderInfo.id = id;
2183 container = c.getInt(containerIndex);
2184 folderInfo.container = container;
2185 folderInfo.screenId = c.getInt(screenIndex);
2186 folderInfo.cellX = c.getInt(cellXIndex);
2187 folderInfo.cellY = c.getInt(cellYIndex);
2188 folderInfo.spanX = 1;
2189 folderInfo.spanY = 1;
2190
2191 // check & update map of what's occupied
2192 if (!checkItemPlacement(occupied, folderInfo)) {
2193 itemsToRemove.add(id);
2194 break;
2195 }
2196
2197 switch (container) {
2198 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2199 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2200 sBgWorkspaceItems.add(folderInfo);
2201 break;
2202 }
2203
2204 if (restored) {
2205 // no special handling required for restored folders
2206 restoredRows.add(id);
2207 }
2208
2209 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2210 sBgFolders.put(folderInfo.id, folderInfo);
2211 break;
2212
2213 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2214 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2215 // Read all Launcher-specific widget details
2216 boolean customWidget = itemType ==
2217 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2218
2219 int appWidgetId = c.getInt(appWidgetIdIndex);
2220 serialNumber= c.getLong(profileIdIndex);
2221 String savedProvider = c.getString(appWidgetProviderIndex);
2222 id = c.getLong(idIndex);
2223 final ComponentName component =
2224 ComponentName.unflattenFromString(savedProvider);
2225
2226 final int restoreStatus = c.getInt(restoredIndex);
2227 =======
2228 serialNumber= c.getLong(profileIdIndex);
2229 user = mUserManager.getUserForSerialNumber(serialNumber);
2230 if (user == null) {
2231 // User has been deleted remove the item.
2232 itemsToRemove.add(id);
2233 continue;
2234 }
2235 >>>>>>> GitAnalyzerPlus_theirs
2236 String savedProvider = c.getString(appWidgetProviderIndex);
2237 id = c.getLong(idIndex);
2238 user = allUsers.get(serialNumber);
2239 if (user == null) {
2240 itemsToRemove.add(id);
2241 continue;
2242 }
2243
2244 final ComponentName component =
2245 ComponentName.unflattenFromString(savedProvider);
2246
2247 final int restoreStatus = c.getInt(restoredIndex);
2248 final boolean isIdValid = (restoreStatus &
2249 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2250 final boolean wasProviderReady = (restoreStatus &
2251 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2252
2253 final LauncherAppWidgetProviderInfo provider =
2254 LauncherModel.getProviderInfo(context,
2255 ComponentName.unflattenFromString(savedProvider),
2256 user);
2257
2258 final boolean isProviderReady = isValidProvider(provider);
2259 if (!isSafeMode && !customWidget &&
2260 wasProviderReady && !isProviderReady) {
2261 String log = "Deleting widget that isn't installed anymore: "
2262 + "id=" + id + " appWidgetId=" + appWidgetId;
2263
2264 Log.e(TAG, log);
2265 Launcher.addDumpLog(TAG, log, false);
2266 itemsToRemove.add(id);
2267 } else {
2268 if (isProviderReady) {
2269 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2270 provider.provider);
2271
2272 int status = restoreStatus;
2273 if (!wasProviderReady) {
2274 // If provider was not previously ready, update the
2275 // status and UI flag.
2276
2277 // Id would be valid only if the widget restore broadcast was🔵
2278 if (isIdValid) {
2279 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2280 } else {
2281 status &= ~LauncherAppWidgetInfo
2282 .FLAG_PROVIDER_NOT_READY;
2283 }
2284 }
2285 appWidgetInfo.restoreStatus = status;
2286 } else {
2287 Log.v(TAG, "Widget restore pending id=" + id
2288 + " appWidgetId=" + appWidgetId
2289 + " status =" + restoreStatus);
2290 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2291 component);
2292 appWidgetInfo.restoreStatus = restoreStatus;
2293 Integer installProgress = installingPkgs.get(component.getPackage🔵
2294
2295 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2296 // Restore has started once.
2297 } else if (installProgress != null) {
2298 // App restore has started. Update the flag
2299 appWidgetInfo.restoreStatus |=
2300 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2301 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2302 Launcher.addDumpLog(TAG,
2303 "Unrestored widget removed: " + component, true);
2304 itemsToRemove.add(id);
2305 continue;
2306 }
2307
2308 appWidgetInfo.installProgress =
2309 installProgress == null ? 0 : installProgress;
2310 }
2311
2312 appWidgetInfo.id = id;
2313 appWidgetInfo.screenId = c.getInt(screenIndex);
2314 appWidgetInfo.cellX = c.getInt(cellXIndex);
2315 appWidgetInfo.cellY = c.getInt(cellYIndex);
2316 appWidgetInfo.spanX = c.getInt(spanXIndex);
2317 appWidgetInfo.spanY = c.getInt(spanYIndex);
2318 appWidgetInfo.user = user;
2319
2320 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2321 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2322 Log.e(TAG, "Widget found where container != " +
2323 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2324 continue;
2325 }
2326
2327 appWidgetInfo.container = container;
2328 // check & update map of what's occupied
2329 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2330 itemsToRemove.add(id);
2331 break;
2332 }
2333
2334 if (!customWidget) {
2335 String providerName =
2336 appWidgetInfo.providerName.flattenToString();
2337 if (!providerName.equals(savedProvider) ||
2338 (appWidgetInfo.restoreStatus != restoreStatus)) {
2339 ContentValues values = new ContentValues();
2340 values.put(
2341 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2342 providerName);
2343 values.put(LauncherSettings.Favorites.RESTORED,
2344 appWidgetInfo.restoreStatus);
2345 updateItem(id, values);
2346 }
2347 }
2348 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2349 sBgAppWidgets.add(appWidgetInfo);
2350 }
2351 break;
2352 }
2353 } catch (Exception e) {
2354 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2355 }
2356 }
2357 } finally {
2358 if (c != null) {
2359 c.close();
2360 }
2361 }
2362
2363 // Break early if we've stopped loading
2364 if (mStopped) {
2365 clearSBgDataStructures();
2366 return;
2367 }
2368
2369 if (itemsToRemove.size() > 0) {
2370 // Remove dead items
2371 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2372 Utilities.createDbSelectionQuery(
2373 LauncherSettings.Favorites._ID, itemsToRemove), null);
2374 if (DEBUG_LOADERS) {
2375 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2376 LauncherSettings.Favorites._ID, itemsToRemove));
2377 }
2378
2379 // Remove any empty folder
2380 for (long folderId : LauncherAppState.getLauncherProvider()
2381 .deleteEmptyFolders()) {
2382 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2383 sBgFolders.remove(folderId);
2384 sBgItemsIdMap.remove(folderId);
2385 }
2386 }
2387
2388 if (restoredRows.size() > 0) {
2389 // Update restored items that no longer require special handling
2390 ContentValues values = new ContentValues();
2391 values.put(LauncherSettings.Favorites.RESTORED, 0);
2392 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2393 Utilities.createDbSelectionQuery(
2394 LauncherSettings.Favorites._ID, restoredRows), null);
2395 }
2396
2397 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2398 context.registerReceiver(new AppsAvailabilityCheck(),
2399 new IntentFilter(StartupReceiver.SYSTEM_READY),
2400 null, sWorker);
2401 }
2402
2403 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2404
2405 // Remove any empty screens
2406 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2407 for (ItemInfo item: sBgItemsIdMap) {
2408 long screenId = item.screenId;
2409 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2410 unusedScreens.contains(screenId)) {
2411 unusedScreens.remove(screenId);
2412 }
2413 }
2414
2415 // If there are any empty screens remove them, and update.
2416 if (unusedScreens.size() != 0) {
2417 sBgWorkspaceScreens.removeAll(unusedScreens);
2418 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2419 }
2420
2421 if (DEBUG_LOADERS) {
2422 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2423 Log.d(TAG, "workspace layout: ");
2424 int nScreens = occupied.size();
2425 for (int y = 0; y < countY; y++) {
2426 String line = "";
2427
2428 for (int i = 0; i < nScreens; i++) {
2429 long screenId = occupied.keyAt(i);
2430 if (screenId > 0) {
2431 line += " | ";
2432 }
2433 ItemInfo[][] screen = occupied.valueAt(i);
2434 for (int x = 0; x < countX; x++) {
2435 if (x < screen.length && y < screen[x].length) {
2436 line += (screen[x][y] != null) ? "#" : ".";
2437 } else {
2438 line += "!";
2439 }
2440 }
2441 }
2442 Log.d(TAG, "[ " + line + " ]");
2443 }
2444 }
2445 }
2446 }
2447
2448 /**
2449 * Partially updates the item without any notification. Must be called on the worker thread.
2450 */
2451 private void updateItem(long itemId, ContentValues update) {
2452 mContext.getContentResolver().update(
2453 LauncherSettings.Favorites.CONTENT_URI,
2454 update,
2455 BaseColumns._ID + "= ?",
2456 new String[]{Long.toString(itemId)});
2457 }
2458
2459 /** Filters the set of items who are directly or indirectly (via another container) on the
2460 * specified screen. */
2461 private void filterCurrentWorkspaceItems(long currentScreenId,
2462 ArrayList<ItemInfo> allWorkspaceItems,
2463 ArrayList<ItemInfo> currentScreenItems,
2464 ArrayList<ItemInfo> otherScreenItems) {
2465 // Purge any null ItemInfos
2466 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2467 while (iter.hasNext()) {
2468 ItemInfo i = iter.next();
2469 if (i == null) {
2470 iter.remove();
2471 }
2472 }
2473
2474 // Order the set of items by their containers first, this allows use to walk through the
2475 // list sequentially, build up a list of containers that are in the specified screen,
2476 // as well as all items in those containers.
2477 Set<Long> itemsOnScreen = new HashSet<Long>();
2478 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2479 @Override
2480 public int compare(ItemInfo lhs, ItemInfo rhs) {
2481 return (int) (lhs.container - rhs.container);
2482 }
2483 });
2484 for (ItemInfo info : allWorkspaceItems) {
2485 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2486 if (info.screenId == currentScreenId) {
2487 currentScreenItems.add(info);
2488 itemsOnScreen.add(info.id);
2489 } else {
2490 otherScreenItems.add(info);
2491 }
2492 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2493 currentScreenItems.add(info);
2494 itemsOnScreen.add(info.id);
2495 } else {
2496 if (itemsOnScreen.contains(info.container)) {
2497 currentScreenItems.add(info);
2498 itemsOnScreen.add(info.id);
2499 } else {
2500 otherScreenItems.add(info);
2501 }
2502 }
2503 }
2504 }
2505
2506 /** Filters the set of widgets which are on the specified screen. */
2507 private void filterCurrentAppWidgets(long currentScreenId,
2508 ArrayList<LauncherAppWidgetInfo> appWidgets,
2509 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2510 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2511
2512 for (LauncherAppWidgetInfo widget : appWidgets) {
2513 if (widget == null) continue;
2514 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2515 widget.screenId == currentScreenId) {
2516 currentScreenWidgets.add(widget);
2517 } else {
2518 otherScreenWidgets.add(widget);
2519 }
2520 }
2521 }
2522
2523 /** Filters the set of folders which are on the specified screen. */
2524 private void filterCurrentFolders(long currentScreenId,
2525 LongArrayMap<ItemInfo> itemsIdMap,
2526 LongArrayMap<FolderInfo> folders,
2527 LongArrayMap<FolderInfo> currentScreenFolders,
2528 LongArrayMap<FolderInfo> otherScreenFolders) {
2529
2530 int total = folders.size();
2531 for (int i = 0; i < total; i++) {
2532 long id = folders.keyAt(i);
2533 FolderInfo folder = folders.valueAt(i);
2534
2535 ItemInfo info = itemsIdMap.get(id);
2536 if (info == null || folder == null) continue;
2537 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2538 info.screenId == currentScreenId) {
2539 currentScreenFolders.put(id, folder);
2540 } else {
2541 otherScreenFolders.put(id, folder);
2542 }
2543 }
2544 }
2545
2546 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2547 * right) */
2548 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2549 final LauncherAppState app = LauncherAppState.getInstance();
2550 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2551 // XXX: review this
2552 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2553 @Override
2554 public int compare(ItemInfo lhs, ItemInfo rhs) {
2555 int cellCountX = (int) profile.numColumns;
2556 int cellCountY = (int) profile.numRows;
2557 int screenOffset = cellCountX * cellCountY;
2558 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2559 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2560 lhs.cellY * cellCountX + lhs.cellX);
2561 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2562 rhs.cellY * cellCountX + rhs.cellX);
2563 return (int) (lr - rr);
2564 }
2565 });
2566 }
2567
2568 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2569 final ArrayList<Long> orderedScreens) {
2570 final Runnable r = new Runnable() {
2571 @Override
2572 public void run() {
2573 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2574 if (callbacks != null) {
2575 callbacks.bindScreens(orderedScreens);
2576 }
2577 }
2578 };
2579 runOnMainThread(r);
2580 }
2581
2582 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2583 final ArrayList<ItemInfo> workspaceItems,
2584 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2585 final LongArrayMap<FolderInfo> folders,
2586 ArrayList<Runnable> deferredBindRunnables) {
2587
2588 final boolean postOnMainThread = (deferredBindRunnables != null);
2589
2590 // Bind the workspace items
2591 int N = workspaceItems.size();
2592 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2593 final int start = i;
2594 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2595 final Runnable r = new Runnable() {
2596 @Override
2597 public void run() {
2598 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2599 if (callbacks != null) {
2600 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2601 false);
2602 }
2603 }
2604 };
2605 if (postOnMainThread) {
2606 synchronized (deferredBindRunnables) {
2607 deferredBindRunnables.add(r);
2608 }
2609 } else {
2610 runOnMainThread(r);
2611 }
2612 }
2613
2614 // Bind the folders
2615 if (!folders.isEmpty()) {
2616 final Runnable r = new Runnable() {
2617 public void run() {
2618 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2619 if (callbacks != null) {
2620 callbacks.bindFolders(folders);
2621 }
2622 }
2623 };
2624 if (postOnMainThread) {
2625 synchronized (deferredBindRunnables) {
2626 deferredBindRunnables.add(r);
2627 }
2628 } else {
2629 runOnMainThread(r);
2630 }
2631 }
2632
2633 // Bind the widgets, one at a time
2634 N = appWidgets.size();
2635 for (int i = 0; i < N; i++) {
2636 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2637 final Runnable r = new Runnable() {
2638 public void run() {
2639 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2640 if (callbacks != null) {
2641 callbacks.bindAppWidget(widget);
2642 }
2643 }
2644 };
2645 if (postOnMainThread) {
2646 deferredBindRunnables.add(r);
2647 } else {
2648 runOnMainThread(r);
2649 }
2650 }
2651 }
2652
2653 /**
2654 * Binds all loaded data to actual views on the main thread.
2655 */
2656 private void bindWorkspace(int synchronizeBindPage) {
2657 final long t = SystemClock.uptimeMillis();
2658 Runnable r;
2659
2660 // Don't use these two variables in any of the callback runnables.
2661 // Otherwise we hold a reference to them.
2662 final Callbacks oldCallbacks = mCallbacks.get();
2663 if (oldCallbacks == null) {
2664 // This launcher has exited and nobody bothered to tell us. Just bail.
2665 Log.w(TAG, "LoaderTask running with no launcher");
2666 return;
2667 }
2668
2669 // Save a copy of all the bg-thread collections
2670 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2671 ArrayList<LauncherAppWidgetInfo> appWidgets =
2672 new ArrayList<LauncherAppWidgetInfo>();
2673 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2674
2675 final LongArrayMap<FolderInfo> folders;
2676 final LongArrayMap<ItemInfo> itemsIdMap;
2677
2678 synchronized (sBgLock) {
2679 workspaceItems.addAll(sBgWorkspaceItems);
2680 appWidgets.addAll(sBgAppWidgets);
2681 orderedScreenIds.addAll(sBgWorkspaceScreens);
2682
2683 folders = sBgFolders.clone();
2684 itemsIdMap = sBgItemsIdMap.clone();
2685 }
2686
2687 final boolean isLoadingSynchronously =
2688 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2689 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2690 oldCallbacks.getCurrentWorkspaceScreen();
2691 if (currScreen >= orderedScreenIds.size()) {
2692 // There may be no workspace screens (just hotseat items and an empty page).
2693 currScreen = PagedView.INVALID_RESTORE_PAGE;
2694 }
2695 final int currentScreen = currScreen;
2696 final long currentScreenId = currentScreen < 0
2697 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2698
2699 // Load all the items that are on the current page first (and in the process, unbind
2700 // all the existing workspace items before we call startBinding() below.
2701 unbindWorkspaceItemsOnMainThread();
2702
2703 // Separate the items that are on the current screen, and all the other remaining items
2704 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2705 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2706 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2707 new ArrayList<LauncherAppWidgetInfo>();
2708 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2709 new ArrayList<LauncherAppWidgetInfo>();
2710 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2711 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2712
2713 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2714 otherWorkspaceItems);
2715 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2716 otherAppWidgets);
2717 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2718 otherFolders);
2719 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2720 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2721
2722 // Tell the workspace that we're about to start binding items
2723 r = new Runnable() {
2724 public void run() {
2725 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2726 if (callbacks != null) {
2727 callbacks.startBinding();
2728 }
2729 }
2730 };
2731 runOnMainThread(r);
2732
2733 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2734
2735 // Load items on the current page
2736 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2737 currentFolders, null);
2738 if (isLoadingSynchronously) {
2739 r = new Runnable() {
2740 public void run() {
2741 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2742 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2743 callbacks.onPageBoundSynchronously(currentScreen);
2744 }
2745 }
2746 };
2747 runOnMainThread(r);
2748 }
2749
2750 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2751 // work until after the first render)
2752 synchronized (mDeferredBindRunnables) {
2753 mDeferredBindRunnables.clear();
2754 }
2755 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2756 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2757
2758 // Tell the workspace that we're done binding items
2759 r = new Runnable() {
2760 public void run() {
2761 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2762 if (callbacks != null) {
2763 callbacks.finishBindingItems();
2764 }
2765
2766 // If we're profiling, ensure this is the last thing in the queue.
2767 if (DEBUG_LOADERS) {
2768 Log.d(TAG, "bound workspace in "
2769 + (SystemClock.uptimeMillis()-t) + "ms");
2770 }
2771
2772 mIsLoadingAndBindingWorkspace = false;
2773 }
2774 };
2775 if (isLoadingSynchronously) {
2776 synchronized (mDeferredBindRunnables) {
2777 mDeferredBindRunnables.add(r);
2778 }
2779 } else {
2780 runOnMainThread(r);
2781 }
2782 }
2783
2784 private void loadAndBindAllApps() {
2785 if (DEBUG_LOADERS) {
2786 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2787 }
2788 if (!mAllAppsLoaded) {
2789 loadAllApps();
2790 synchronized (LoaderTask.this) {
2791 if (mStopped) {
2792 return;
2793 }
2794 }
2795 updateIconCache();
2796 synchronized (LoaderTask.this) {
2797 if (mStopped) {
2798 return;
2799 }
2800 mAllAppsLoaded = true;
2801 }
2802 } else {
2803 onlyBindAllApps();
2804 }
2805 }
2806
2807 private void updateIconCache() {
2808 // Ignore packages which have a promise icon.
2809 HashSet<String> packagesToIgnore = new HashSet<>();
2810 synchronized (sBgLock) {
2811 for (ItemInfo info : sBgItemsIdMap) {
2812 if (info instanceof ShortcutInfo) {
2813 ShortcutInfo si = (ShortcutInfo) info;
2814 if (si.isPromise() && si.getTargetComponent() != null) {
2815 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2816 }
2817 } else if (info instanceof LauncherAppWidgetInfo) {
2818 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2819 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2820 packagesToIgnore.add(lawi.providerName.getPackageName());
2821 }
2822 }
2823 }
2824 }
2825 mIconCache.updateDbIcons(packagesToIgnore);
2826 }
2827
2828 private void onlyBindAllApps() {
2829 final Callbacks oldCallbacks = mCallbacks.get();
2830 if (oldCallbacks == null) {
2831 // This launcher has exited and nobody bothered to tell us. Just bail.
2832 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2833 return;
2834 }
2835
2836 // shallow copy
2837 @SuppressWarnings("unchecked")
2838 final ArrayList<AppInfo> list
2839 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2840 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2841 Runnable r = new Runnable() {
2842 public void run() {
2843 final long t = SystemClock.uptimeMillis();
2844 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2845 if (callbacks != null) {
2846 callbacks.bindAllApplications(list);
2847 callbacks.bindAllPackages(widgetList);
2848 }
2849 if (DEBUG_LOADERS) {
2850 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2851 + (SystemClock.uptimeMillis()-t) + "ms");
2852 }
2853 }
2854 };
2855 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2856 if (isRunningOnMainThread) {
2857 r.run();
2858 } else {
2859 mHandler.post(r);
2860 }
2861 }
2862
2863 private void loadAllApps() {
2864 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2865
2866 final Callbacks oldCallbacks = mCallbacks.get();
2867 if (oldCallbacks == null) {
2868 // This launcher has exited and nobody bothered to tell us. Just bail.
2869 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2870 return;
2871 }
2872
2873 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2874
2875 // Clear the list of apps
2876 mBgAllAppsList.clear();
2877 for (UserHandleCompat user : profiles) {
2878 // Query for the set of apps
2879 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2880 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2881 if (DEBUG_LOADERS) {
2882 Log.d(TAG, "getActivityList took "
2883 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2884 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2885 }
2886 // Fail if we don't have any apps
2887 // TODO: Fix this. Only fail for the current user.
2888 if (apps == null || apps.isEmpty()) {
2889 return;
2890 }
2891
2892 // Create the ApplicationInfos
2893 for (int i = 0; i < apps.size(); i++) {
2894 LauncherActivityInfoCompat app = apps.get(i);
2895 // This builds the icon bitmaps.
2896 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2897 }
2898
2899 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2900 if (heuristic != null) {
2901 runAfterBindCompletes(new Runnable() {
2902
2903 @Override
2904 public void run() {
2905 heuristic.processUserApps(apps);
2906 }
2907 });
2908 }
2909 }
2910 // Huh? Shouldn't this be inside the Runnable below?
2911 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2912 mBgAllAppsList.added = new ArrayList<AppInfo>();
2913
2914 // Post callback on main thread
2915 mHandler.post(new Runnable() {
2916 public void run() {
2917
2918 final long bindTime = SystemClock.uptimeMillis();
2919 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2920 if (callbacks != null) {
2921 callbacks.bindAllApplications(added);
2922 if (DEBUG_LOADERS) {
2923 Log.d(TAG, "bound " + added.size() + " apps in "
2924 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2925 }
2926 } else {
2927 Log.i(TAG, "not binding apps: no Launcher activity");
2928 }
2929 }
2930 });
2931 // Cleanup any data stored for a deleted user.
2932 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2933
2934 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
2935 true /* refresh */);
2936 if (DEBUG_LOADERS) {
2937 Log.d(TAG, "Icons processed in "
2938 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2939 }
2940 }
2941
2942 public void dumpState() {
2943 synchronized (sBgLock) {
2944 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2945 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2946 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2947 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2948 }
2949 }
2950 }
2951
2952 /**
2953 * Called when the icons for packages have been updated in the icon cache.
2954 */
2955 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2956 final Callbacks callbacks = getCallback();
2957 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2958 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2959
2960 // If any package icon has changed (app was updated while launcher was dead),
2961 // update the corresponding shortcuts.
2962 synchronized (sBgLock) {
2963 for (ItemInfo info : sBgItemsIdMap) {
2964 if (info instanceof ShortcutInfo && user.equals(info.user)
2965 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2966 ShortcutInfo si = (ShortcutInfo) info;
2967 ComponentName cn = si.getTargetComponent();
2968 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2969 si.updateIcon(mIconCache);
2970 updatedShortcuts.add(si);
2971 }
2972 }
2973 }
2974 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2975 }
2976
2977 if (!updatedShortcuts.isEmpty()) {
2978 final UserHandleCompat userFinal = user;
2979 mHandler.post(new Runnable() {
2980
2981 public void run() {
2982 Callbacks cb = getCallback();
2983 if (cb != null && callbacks == cb) {
2984 cb.bindShortcutsChanged(updatedShortcuts,
2985 new ArrayList<ShortcutInfo>(), userFinal);
2986 }
2987 }
2988 });
2989 }
2990
2991 if (!updatedApps.isEmpty()) {
2992 mHandler.post(new Runnable() {
2993
2994 public void run() {
2995 Callbacks cb = getCallback();
2996 if (cb != null && callbacks == cb) {
2997 cb.bindAppsUpdated(updatedApps);
2998 }
2999 }
3000 });
3001 }
3002
3003 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
3004 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
3005 }
3006
3007 void enqueuePackageUpdated(PackageUpdatedTask task) {
3008 sWorker.post(task);
3009 }
3010
3011 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
3012
3013 @Override
3014 public void onReceive(Context context, Intent intent) {
3015 synchronized (sBgLock) {
3016 final LauncherAppsCompat launcherApps = LauncherAppsCompat
3017 .getInstance(mApp.getContext());
3018 final PackageManager manager = context.getPackageManager();
3019 final ArrayList<String> packagesRemoved = new ArrayList<String>();
3020 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
3021 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
3022 UserHandleCompat user = entry.getKey();
3023 packagesRemoved.clear();
3024 packagesUnavailable.clear();
3025 for (String pkg : entry.getValue()) {
3026 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
3027 boolean packageOnSdcard = launcherApps.isAppEnabled(
3028 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
3029 if (packageOnSdcard) {
3030 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
3031 packagesUnavailable.add(pkg);
3032 } else {
3033 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
3034 packagesRemoved.add(pkg);
3035 }
3036 }
3037 }
3038 if (!packagesRemoved.isEmpty()) {
3039 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
3040 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
3041 }
3042 if (!packagesUnavailable.isEmpty()) {
3043 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
3044 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
3045 }
3046 }
3047 sPendingPackages.clear();
3048 }
3049 }
3050 }
3051
3052 private class PackageUpdatedTask implements Runnable {
3053 int mOp;
3054 String[] mPackages;
3055 UserHandleCompat mUser;
3056
3057 public static final int OP_NONE = 0;
3058 public static final int OP_ADD = 1;
3059 public static final int OP_UPDATE = 2;
3060 public static final int OP_REMOVE = 3; // uninstlled
3061 public static final int OP_UNAVAILABLE = 4; // external media unmounted
3062
3063
3064 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
3065 mOp = op;
3066 mPackages = packages;
3067 mUser = user;
3068 }
3069
3070 public void run() {
3071 if (!mHasLoaderCompletedOnce) {
3072 // Loader has not yet run.
3073 return;
3074 }
3075 final Context context = mApp.getContext();
3076
3077 final String[] packages = mPackages;
3078 final int N = packages.length;
3079 switch (mOp) {
3080 case OP_ADD: {
3081 for (int i=0; i<N; i++) {
3082 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3083 mIconCache.updateIconsForPkg(packages[i], mUser);
3084 mBgAllAppsList.addPackage(context, packages[i], mUser);
3085 }
3086
3087 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3088 if (heuristic != null) {
3089 heuristic.processPackageAdd(mPackages);
3090 }
3091 break;
3092 }
3093 case OP_UPDATE:
3094 for (int i=0; i<N; i++) {
3095 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3096 mIconCache.updateIconsForPkg(packages[i], mUser);
3097 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3098 mApp.getWidgetCache().removePackage(packages[i], mUser);
3099 }
3100 break;
3101 case OP_REMOVE: {
3102 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3103 if (heuristic != null) {
3104 heuristic.processPackageRemoved(mPackages);
3105 }
3106 for (int i=0; i<N; i++) {
3107 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3108 mIconCache.removeIconsForPkg(packages[i], mUser);
3109 }
3110 // Fall through
3111 }
3112 case OP_UNAVAILABLE:
3113 for (int i=0; i<N; i++) {
3114 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3115 mBgAllAppsList.removePackage(packages[i], mUser);
3116 mApp.getWidgetCache().removePackage(packages[i], mUser);
3117 }
3118 break;
3119 }
3120
3121 ArrayList<AppInfo> added = null;
3122 ArrayList<AppInfo> modified = null;
3123 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3124
3125 if (mBgAllAppsList.added.size() > 0) {
3126 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3127 mBgAllAppsList.added.clear();
3128 }
3129 if (mBgAllAppsList.modified.size() > 0) {
3130 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3131 mBgAllAppsList.modified.clear();
3132 }
3133 if (mBgAllAppsList.removed.size() > 0) {
3134 removedApps.addAll(mBgAllAppsList.removed);
3135 mBgAllAppsList.removed.clear();
3136 }
3137
3138 final Callbacks callbacks = getCallback();
3139 if (callbacks == null) {
3140 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3141 return;
3142 }
3143
3144 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3145 new HashMap<ComponentName, AppInfo>();
3146
3147 if (added != null) {
3148 addAppsToAllApps(context, added);
3149 for (AppInfo ai : added) {
3150 addedOrUpdatedApps.put(ai.componentName, ai);
3151 }
3152 }
3153
3154 if (modified != null) {
3155 final ArrayList<AppInfo> modifiedFinal = modified;
3156 for (AppInfo ai : modified) {
3157 addedOrUpdatedApps.put(ai.componentName, ai);
3158 }
3159
3160 mHandler.post(new Runnable() {
3161 public void run() {
3162 Callbacks cb = getCallback();
3163 if (callbacks == cb && cb != null) {
3164 callbacks.bindAppsUpdated(modifiedFinal);
3165 }
3166 }
3167 });
3168 }
3169
3170 // Update shortcut infos
3171 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3172 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3173 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3174 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3175
3176 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3177 synchronized (sBgLock) {
3178 for (ItemInfo info : sBgItemsIdMap) {
3179 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3180 ShortcutInfo si = (ShortcutInfo) info;
3181 boolean infoUpdated = false;
3182 boolean shortcutUpdated = false;
3183
3184 // Update shortcuts which use iconResource.
3185 if ((si.iconResource != null)
3186 && packageSet.contains(si.iconResource.packageName)) {
3187 Bitmap icon = Utilities.createIconBitmap(
3188 si.iconResource.packageName,
3189 si.iconResource.resourceName, context);
3190 if (icon != null) {
3191 si.setIcon(icon);
3192 si.usingFallbackIcon = false;
3193 infoUpdated = true;
3194 }
3195 }
3196
3197 ComponentName cn = si.getTargetComponent();
3198 if (cn != null && packageSet.contains(cn.getPackageName())) {
3199 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3200
3201 if (si.isPromise()) {
3202 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3203 // Auto install icon
3204 PackageManager pm = context.getPackageManager();
3205 ResolveInfo matched = pm.resolveActivity(
3206 new Intent(Intent.ACTION_MAIN)
3207 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3208 PackageManager.MATCH_DEFAULT_ONLY);
3209 if (matched == null) {
3210 // Try to find the best match activity.
3211 Intent intent = pm.getLaunchIntentForPackage(
3212 cn.getPackageName());
3213 if (intent != null) {
3214 cn = intent.getComponent();
3215 appInfo = addedOrUpdatedApps.get(cn);
3216 }
3217
3218 if ((intent == null) || (appInfo == null)) {
3219 removedShortcuts.add(si);
3220 continue;
3221 }
3222 si.promisedIntent = intent;
3223 }
3224 }
3225
3226 // Restore the shortcut.
3227 if (appInfo != null) {
3228 si.flags = appInfo.flags;
3229 }
3230
3231 si.intent = si.promisedIntent;
3232 si.promisedIntent = null;
3233 si.status = ShortcutInfo.DEFAULT;
3234 infoUpdated = true;
3235 si.updateIcon(mIconCache);
3236 }
3237
3238 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3239 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3240 si.updateIcon(mIconCache);
3241 si.title = Utilities.trim(appInfo.title);
3242 si.contentDescription = appInfo.contentDescription;
3243 infoUpdated = true;
3244 }
3245
3246 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3247 // Since package was just updated, the target must be available now.
3248 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3249 shortcutUpdated = true;
3250 }
3251 }
3252
3253 if (infoUpdated || shortcutUpdated) {
3254 updatedShortcuts.add(si);
3255 }
3256 if (infoUpdated) {
3257 updateItemInDatabase(context, si);
3258 }
3259 } else if (info instanceof LauncherAppWidgetInfo) {
3260 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3261 if (mUser.equals(widgetInfo.user)
3262 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3263 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3264 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3265 widgets.add(widgetInfo);
3266 updateItemInDatabase(context, widgetInfo);
3267 }
3268 }
3269 }
3270 }
3271
3272 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3273 mHandler.post(new Runnable() {
3274
3275 public void run() {
3276 Callbacks cb = getCallback();
3277 if (callbacks == cb && cb != null) {
3278 callbacks.bindShortcutsChanged(
3279 updatedShortcuts, removedShortcuts, mUser);
3280 }
3281 }
3282 });
3283 if (!removedShortcuts.isEmpty()) {
3284 deleteItemsFromDatabase(context, removedShortcuts);
3285 }
3286 }
3287 if (!widgets.isEmpty()) {
3288 mHandler.post(new Runnable() {
3289 public void run() {
3290 Callbacks cb = getCallback();
3291 if (callbacks == cb && cb != null) {
3292 callbacks.bindWidgetsRestored(widgets);
3293 }
3294 }
3295 });
3296 }
3297 }
3298
3299 final ArrayList<String> removedPackageNames =
3300 new ArrayList<String>();
3301 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3302 // Mark all packages in the broadcast to be removed
3303 removedPackageNames.addAll(Arrays.asList(packages));
3304 } else if (mOp == OP_UPDATE) {
3305 // Mark disabled packages in the broadcast to be removed
3306 for (int i=0; i<N; i++) {
3307 if (isPackageDisabled(context, packages[i], mUser)) {
3308 removedPackageNames.add(packages[i]);
3309 }
3310 }
3311 }
3312
3313 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3314 final int removeReason;
3315 if (mOp == OP_UNAVAILABLE) {
3316 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3317 } else {
3318 // Remove all the components associated with this package
3319 for (String pn : removedPackageNames) {
3320 deletePackageFromDatabase(context, pn, mUser);
3321 }
3322 // Remove all the specific components
3323 for (AppInfo a : removedApps) {
3324 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3325 deleteItemsFromDatabase(context, infos);
3326 }
3327 removeReason = 0;
3328 }
3329
3330 // Remove any queued items from the install queue
3331 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3332 // Call the components-removed callback
3333 mHandler.post(new Runnable() {
3334 public void run() {
3335 Callbacks cb = getCallback();
3336 if (callbacks == cb && cb != null) {
3337 callbacks.bindComponentsRemoved(
3338 removedPackageNames, removedApps, mUser, removeReason);
3339 }
3340 }
3341 });
3342 }
3343
3344 // onProvidersChanged method (API >= 17) already refreshed the widget list
3345 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
3346
3347 // Write all the logs to disk
3348 mHandler.post(new Runnable() {
3349 public void run() {
3350 Callbacks cb = getCallback();
3351 if (callbacks == cb && cb != null) {
3352 callbacks.dumpLogsToLocalData();
3353 }
3354 }
3355 });
3356 }
3357 }
3358
3359 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
3360 boolean refresh) {
3361 ArrayList<LauncherAppWidgetProviderInfo> results =
3362 new ArrayList<LauncherAppWidgetProviderInfo>();
3363 try {
3364 synchronized (sBgLock) {
3365 if (sBgWidgetProviders == null || refresh) {
3366 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
3367 = new HashMap<>();
3368 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
3369 LauncherAppWidgetProviderInfo info;
3370
3371 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
3372 for (AppWidgetProviderInfo pInfo : widgets) {
3373 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3374 UserHandleCompat user = wm.getUser(info);
3375 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3376 }
3377
3378 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3379 for (CustomAppWidget widget : customWidgets) {
3380 info = new LauncherAppWidgetProviderInfo(context, widget);
3381 UserHandleCompat user = wm.getUser(info);
3382 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3383 }
3384 // Replace the global list at the very end, so that if there is an exception,
3385 // previously loaded provider list is used.
3386 sBgWidgetProviders = tmpWidgetProviders;
3387 }
3388 results.addAll(sBgWidgetProviders.values());
3389 return results;
3390 }
3391 } catch (Exception e) {
3392 if (e.getCause() instanceof TransactionTooLargeException) {
3393 // the returned value may be incomplete and will not be refreshed until the next
3394 // time Launcher starts.
3395 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3396 // onResume is called to refresh the widget provider list.
3397 synchronized (sBgLock) {
3398 if (sBgWidgetProviders != null) {
3399 results.addAll(sBgWidgetProviders.values());
3400 }
3401 return results;
3402 }
3403 } else {
3404 throw e;
3405 }
3406 }
3407 }
3408
3409 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3410 UserHandleCompat user) {
3411 synchronized (sBgLock) {
3412 if (sBgWidgetProviders == null) {
3413 getWidgetProviders(ctx, false /* refresh */);
3414 }
3415 return sBgWidgetProviders.get(new ComponentKey(name, user));
3416 }
3417 }
3418
3419 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
3420 final boolean refresh) {
3421
3422 runOnWorkerThread(new Runnable() {
3423 @Override
3424 public void run() {
3425 updateWidgetsModel(context, refresh);
3426 final WidgetsModel model = mBgWidgetsModel.clone();
3427
3428 mHandler.post(new Runnable() {
3429 @Override
3430 public void run() {
3431 Callbacks cb = getCallback();
3432 if (callbacks == cb && cb != null) {
3433 callbacks.bindAllPackages(model);
3434 }
3435 }
3436 });
3437 // update the Widget entries inside DB on the worker thread.
3438 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3439 model.getRawList());
3440 }
3441 });
3442 }
3443
3444 /**
3445 * Returns a list of ResolveInfos/AppWidgetInfos.
3446 *
3447 * @see #loadAndBindWidgetsAndShortcuts
3448 */
3449 @Thunk void updateWidgetsModel(Context context, boolean refresh) {
3450 PackageManager packageManager = context.getPackageManager();
3451 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3452 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3453 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3454 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3455 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3456 }
3457
3458 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3459 UserHandleCompat user) {
3460 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3461 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3462 }
3463
3464 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3465 UserHandleCompat user) {
3466 if (cn == null) {
3467 return false;
3468 }
3469 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3470 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3471 return false;
3472 }
3473 return launcherApps.isActivityEnabledForProfile(cn, user);
3474 }
3475
3476 public static boolean isValidPackage(Context context, String packageName,
3477 UserHandleCompat user) {
3478 if (packageName == null) {
3479 return false;
3480 }
3481 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3482 return launcherApps.isPackageEnabledForProfile(packageName, user);
3483 }
3484
3485 /**
3486 * Make an ShortcutInfo object for a restored application or shortcut item that points
3487 * to a package that is not yet installed on the system.
3488 */
3489 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
3490 int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
3491 final ShortcutInfo info = new ShortcutInfo();
3492 info.user = UserHandleCompat.myUserHandle();
3493
3494 Bitmap icon = iconInfo.loadIcon(c, info, context);
3495 // the fallback icon
3496 if (icon == null) {
3497 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3498 } else {
3499 info.setIcon(icon);
3500 }
3501
3502 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3503 String title = (c != null) ? c.getString(titleIndex) : null;
3504 if (!TextUtils.isEmpty(title)) {
3505 info.title = Utilities.trim(title);
3506 }
3507 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3508 if (TextUtils.isEmpty(info.title)) {
3509 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3510 }
3511 } else {
3512 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3513 }
3514
3515 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3516 info.itemType = itemType;
3517 info.promisedIntent = intent;
3518 info.status = promiseType;
3519 return info;
3520 }
3521
3522 /**
3523 * Make an Intent object for a restored application or shortcut item that points
3524 * to the market page for the item.
3525 */
3526 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3527 ComponentName componentName = intent.getComponent();
3528 return getMarketIntent(componentName.getPackageName());
3529 }
3530
3531 static Intent getMarketIntent(String packageName) {
3532 return new Intent(Intent.ACTION_VIEW)
3533 .setData(new Uri.Builder()
3534 .scheme("market")
3535 .authority("details")
3536 .appendQueryParameter("id", packageName)
3537 .build());
3538 }
3539
3540 /**
3541 * Make an ShortcutInfo object for a shortcut that is an application.
3542 *
3543 * If c is not null, then it will be used to fill in missing data like the title and icon.
3544 */
3545 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3546 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3547 boolean allowMissingTarget, boolean useLowResIcon) {
3548 if (user == null) {
3549 Log.d(TAG, "Null user found in getShortcutInfo");
3550 return null;
3551 }
3552
3553 ComponentName componentName = intent.getComponent();
3554 if (componentName == null) {
3555 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3556 return null;
3557 }
3558
3559 Intent newIntent = new Intent(intent.getAction(), null);
3560 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3561 newIntent.setComponent(componentName);
3562 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3563 if ((lai == null) && !allowMissingTarget) {
3564 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3565 return null;
3566 }
3567
3568 final ShortcutInfo info = new ShortcutInfo();
3569 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3570 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3571 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3572 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3573 }
3574
3575 // from the db
3576 if (TextUtils.isEmpty(info.title) && c != null) {
3577 info.title = Utilities.trim(c.getString(titleIndex));
3578 }
3579
3580 // fall back to the class name of the activity
3581 if (info.title == null) {
3582 info.title = componentName.getClassName();
3583 }
3584
3585 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3586 info.user = user;
3587 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3588 if (lai != null) {
3589 info.flags = AppInfo.initFlags(lai);
3590 }
3591 return info;
3592 }
3593
3594 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3595 ItemInfoFilter f) {
3596 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3597 for (ItemInfo i : infos) {
3598 if (i instanceof ShortcutInfo) {
3599 ShortcutInfo info = (ShortcutInfo) i;
3600 ComponentName cn = info.getTargetComponent();
3601 if (cn != null && f.filterItem(null, info, cn)) {
3602 filtered.add(info);
3603 }
3604 } else if (i instanceof FolderInfo) {
3605 FolderInfo info = (FolderInfo) i;
3606 for (ShortcutInfo s : info.contents) {
3607 ComponentName cn = s.getTargetComponent();
3608 if (cn != null && f.filterItem(info, s, cn)) {
3609 filtered.add(s);
3610 }
3611 }
3612 } else if (i instanceof LauncherAppWidgetInfo) {
3613 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3614 ComponentName cn = info.providerName;
3615 if (cn != null && f.filterItem(null, info, cn)) {
3616 filtered.add(info);
3617 }
3618 }
3619 }
3620 return new ArrayList<ItemInfo>(filtered);
3621 }
3622
3623 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3624 final UserHandleCompat user) {
3625 ItemInfoFilter filter = new ItemInfoFilter() {
3626 @Override
3627 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3628 if (info.user == null) {
3629 return cn.equals(cname);
3630 } else {
3631 return cn.equals(cname) && info.user.equals(user);
3632 }
3633 }
3634 };
3635 return filterItemInfos(sBgItemsIdMap, filter);
3636 }
3637
3638 /**
3639 * Make an ShortcutInfo object for a shortcut that isn't an application.
3640 */
3641 @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
3642 int titleIndex, CursorIconInfo iconInfo) {
3643 final ShortcutInfo info = new ShortcutInfo();
3644 // Non-app shortcuts are only supported for current user.
3645 info.user = UserHandleCompat.myUserHandle();
3646 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3647
3648 // TODO: If there's an explicit component and we can't install that, delete it.
3649
3650 info.title = Utilities.trim(c.getString(titleIndex));
3651
3652 Bitmap icon = iconInfo.loadIcon(c, info, context);
3653 // the fallback icon
3654 if (icon == null) {
3655 icon = mIconCache.getDefaultIcon(info.user);
3656 info.usingFallbackIcon = true;
3657 }
3658 info.setIcon(icon);
3659 return info;
3660 }
3661
3662 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3663 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3664 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3665 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3666
3667 if (intent == null) {
3668 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3669 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3670 return null;
3671 }
3672
3673 Bitmap icon = null;
3674 boolean customIcon = false;
3675 ShortcutIconResource iconResource = null;
3676
3677 if (bitmap instanceof Bitmap) {
3678 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3679 customIcon = true;
3680 } else {
3681 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3682 if (extra instanceof ShortcutIconResource) {
3683 iconResource = (ShortcutIconResource) extra;
3684 icon = Utilities.createIconBitmap(iconResource.packageName,
3685 iconResource.resourceName, context);
3686 }
3687 }
3688
3689 final ShortcutInfo info = new ShortcutInfo();
3690
3691 // Only support intents for current user for now. Intents sent from other
3692 // users wouldn't get here without intent forwarding anyway.
3693 info.user = UserHandleCompat.myUserHandle();
3694 if (icon == null) {
3695 icon = mIconCache.getDefaultIcon(info.user);
3696 info.usingFallbackIcon = true;
3697 }
3698 info.setIcon(icon);
3699
3700 info.title = Utilities.trim(name);
3701 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3702 info.intent = intent;
3703 info.customIcon = customIcon;
3704 info.iconResource = iconResource;
3705
3706 return info;
3707 }
3708
3709 /**
3710 * Return an existing FolderInfo object if we have encountered this ID previously,
3711 * or make a new one.
3712 */
3713 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3714 // See if a placeholder was created for us already
3715 FolderInfo folderInfo = folders.get(id);
3716 if (folderInfo == null) {
3717 // No placeholder -- create a new instance
3718 folderInfo = new FolderInfo();
3719 folders.put(id, folderInfo);
3720 }
3721 return folderInfo;
3722 }
3723
3724
3725 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3726 return (provider != null) && (provider.provider != null)
3727 && (provider.provider.getPackageName() != null);
3728 }
3729
3730 public void dumpState() {
3731 Log.d(TAG, "mCallbacks=" + mCallbacks);
3732 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3733 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3734 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3735 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3736 if (mLoaderTask != null) {
3737 mLoaderTask.dumpState();
3738 } else {
3739 Log.d(TAG, "mLoaderTask=null");
3740 }
3741 }
3742
3743 public Callbacks getCallback() {
3744 return mCallbacks != null ? mCallbacks.get() : null;
3745 }
3746
3747 /**
3748 * @return {@link FolderInfo} if its already loaded.
3749 */
3750 public FolderInfo findFolderById(Long folderId) {
3751 synchronized (sBgLock) {
3752 return sBgFolders.get(folderId);
3753 }
3754 }
3755
3756 /**
3757 * @return the looper for the worker thread which can be used to start background tasks.
3758 */
3759 public static Looper getWorkerLooper() {
3760 return sWorkerThread.getLooper();
3761 }
3762 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.Intent.ShortcutIconResource;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.content.pm.ResolveInfo;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.net.Uri;
36 import android.os.Build;
37 import android.os.Environment;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Parcelable;
42 import android.os.Process;
43 import android.os.SystemClock;
44 import android.os.TransactionTooLargeException;
45 import android.provider.BaseColumns;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.LongSparseArray;
49 import android.util.Pair;
50
51 import com.android.launcher3.compat.AppWidgetManagerCompat;
52 import com.android.launcher3.compat.LauncherActivityInfoCompat;
53 import com.android.launcher3.compat.LauncherAppsCompat;
54 import com.android.launcher3.compat.PackageInstallerCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.UserHandleCompat;
57 import com.android.launcher3.compat.UserManagerCompat;
58 import com.android.launcher3.model.WidgetsModel;
59 import com.android.launcher3.util.ComponentKey;
60 import com.android.launcher3.util.CursorIconInfo;
61 import com.android.launcher3.util.LongArrayMap;
62 import com.android.launcher3.util.ManagedProfileHeuristic;
63 import com.android.launcher3.util.Thunk;
64
65 import java.lang.ref.WeakReference;
66 import java.net.URISyntaxException;
67 import java.security.InvalidParameterException;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map.Entry;
78 import java.util.Set;
79
80 /**
81 * Maintains in-memory state of the Launcher. It is expected that there should be only one
82 * LauncherModel object held in a static. Also provide APIs for updating the database state
83 * for the Launcher.
84 */
85 public class LauncherModel extends BroadcastReceiver
86 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
87 static final boolean DEBUG_LOADERS = false;
88 private static final boolean DEBUG_RECEIVER = false;
89 private static final boolean REMOVE_UNRESTORED_ICONS = true;
90
91 static final String TAG = "Launcher.Model";
92
93 public static final int LOADER_FLAG_NONE = 0;
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
96
97 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
98 private static final long INVALID_SCREEN_ID = -1L;
99
100 @Thunk final boolean mAppsCanBeOnRemoveableStorage;
101 private final boolean mOldContentProviderExists;
102
103 @Thunk final LauncherAppState mApp;
104 @Thunk final Object mLock = new Object();
105 @Thunk DeferredHandler mHandler = new DeferredHandler();
106 @Thunk LoaderTask mLoaderTask;
107 @Thunk boolean mIsLoaderTaskRunning;
108 @Thunk boolean mHasLoaderCompletedOnce;
109
110 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
111
112 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
113 static {
114 sWorkerThread.start();
115 }
116 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
117
118 // We start off with everything not loaded. After that, we assume that
119 // our monitoring of the package manager provides all updates and we never
120 // need to do a requery. These are only ever touched from the loader thread.
121 @Thunk boolean mWorkspaceLoaded;
122 @Thunk boolean mAllAppsLoaded;
123
124 // When we are loading pages synchronously, we can't just post the binding of items on the side
125 // pages as this delays the rotation process. Instead, we wait for a callback from the first
126 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
127 // a normal load, we also clear this set of Runnables.
128 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
129
130 /**
131 * Set of runnables to be called on the background thread after the workspace binding
132 * is complete.
133 */
134 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
135
136 @Thunk WeakReference<Callbacks> mCallbacks;
137
138 // < only access in worker thread >
139 AllAppsList mBgAllAppsList;
140 // Entire list of widgets.
141 WidgetsModel mBgWidgetsModel;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170
171 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
172 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
173 new HashMap<UserHandleCompat, HashSet<String>>();
174
175 // </ only access in worker thread >
176
177 @Thunk IconCache mIconCache;
178
179 @Thunk final LauncherAppsCompat mLauncherApps;
180 @Thunk final UserManagerCompat mUserManager;
181
182 public interface Callbacks {
183 public boolean setLoadOnResume();
184 public int getCurrentWorkspaceScreen();
185 public void startBinding();
186 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
187 boolean forceAnimateIcons);
188 public void bindScreens(ArrayList<Long> orderedScreenIds);
189 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
190 public void bindFolders(LongArrayMap<FolderInfo> folders);
191 public void finishBindingItems();
192 public void bindAppWidget(LauncherAppWidgetInfo info);
193 public void bindAllApplications(ArrayList<AppInfo> apps);
194 public void bindAppsAdded(ArrayList<Long> newScreens,
195 ArrayList<ItemInfo> addNotAnimated,
196 ArrayList<ItemInfo> addAnimated,
197 ArrayList<AppInfo> addedApps);
198 public void bindAppsUpdated(ArrayList<AppInfo> apps);
199 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
200 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
201 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
202 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
203 public void bindComponentsRemoved(ArrayList<String> packageNames,
204 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
205 public void bindAllPackages(WidgetsModel model);
206 public void bindSearchablesChanged();
207 public boolean isAllAppsButtonRank(int rank);
208 public void onPageBoundSynchronously(int page);
209 public void dumpLogsToLocalData();
210 }
211
212 public interface ItemInfoFilter {
213 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
214 }
215
216 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
217 Context context = app.getContext();
218
219 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
220 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
221 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
222 // resource string.
223 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
224 ProviderInfo providerInfo =
225 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
226 ProviderInfo redirectProvider =
227 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
228
229 Log.d(TAG, "Old launcher provider: " + oldProvider);
230 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
231
232 if (mOldContentProviderExists) {
233 Log.d(TAG, "Old launcher provider exists.");
234 } else {
235 Log.d(TAG, "Old launcher provider does not exist.");
236 }
237
238 mApp = app;
239 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
240 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
241 mIconCache = iconCache;
242
243 mLauncherApps = LauncherAppsCompat.getInstance(context);
244 mUserManager = UserManagerCompat.getInstance(context);
245 }
246
247 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
248 * posted on the main thread handler. */
249 @Thunk void runOnMainThread(Runnable r) {
250 if (sWorkerThread.getThreadId() == Process.myTid()) {
251 // If we are on the worker thread, post onto the main handler
252 mHandler.post(r);
253 } else {
254 r.run();
255 }
256 }
257
258 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
259 * posted on the worker thread handler. */
260 private static void runOnWorkerThread(Runnable r) {
261 if (sWorkerThread.getThreadId() == Process.myTid()) {
262 r.run();
263 } else {
264 // If we are not on the worker thread, then post to the worker handler
265 sWorker.post(r);
266 }
267 }
268
269 /**
270 * Runs the specified runnable after the loader is complete
271 */
272 @Thunk void runAfterBindCompletes(Runnable r) {
273 if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
274 synchronized (mBindCompleteRunnables) {
275 mBindCompleteRunnables.add(r);
276 }
277 } else {
278 runOnWorkerThread(r);
279 }
280 }
281
282 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
283 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
284 }
285
286 public void setPackageState(final PackageInstallInfo installInfo) {
287 Runnable updateRunnable = new Runnable() {
288
289 @Override
290 public void run() {
291 synchronized (sBgLock) {
292 final HashSet<ItemInfo> updates = new HashSet<>();
293
294 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
295 // Ignore install success events as they are handled by Package add events.
296 return;
297 }
298
299 for (ItemInfo info : sBgItemsIdMap) {
300 if (info instanceof ShortcutInfo) {
301 ShortcutInfo si = (ShortcutInfo) info;
302 ComponentName cn = si.getTargetComponent();
303 if (si.isPromise() && (cn != null)
304 && installInfo.packageName.equals(cn.getPackageName())) {
305 si.setInstallProgress(installInfo.progress);
306
307 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
308 // Mark this info as broken.
309 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
310 }
311 updates.add(si);
312 }
313 }
314 }
315
316 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
317 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
318 widget.installProgress = installInfo.progress;
319 updates.add(widget);
320 }
321 }
322
323 if (!updates.isEmpty()) {
324 // Push changes to the callback.
325 Runnable r = new Runnable() {
326 public void run() {
327 Callbacks callbacks = getCallback();
328 if (callbacks != null) {
329 callbacks.bindRestoreItemsChange(updates);
330 }
331 }
332 };
333 mHandler.post(r);
334 }
335 }
336 }
337 };
338 runOnWorkerThread(updateRunnable);
339 }
340
341 /**
342 * Updates the icons and label of all pending icons for the provided package name.
343 */
344 public void updateSessionDisplayInfo(final String packageName) {
345 Runnable updateRunnable = new Runnable() {
346
347 @Override
348 public void run() {
349 synchronized (sBgLock) {
350 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
351 final UserHandleCompat user = UserHandleCompat.myUserHandle();
352
353 for (ItemInfo info : sBgItemsIdMap) {
354 if (info instanceof ShortcutInfo) {
355 ShortcutInfo si = (ShortcutInfo) info;
356 ComponentName cn = si.getTargetComponent();
357 if (si.isPromise() && (cn != null)
358 && packageName.equals(cn.getPackageName())) {
359 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
360 // For auto install apps update the icon as well as label.
361 mIconCache.getTitleAndIcon(si,
362 si.promisedIntent, user,
363 si.shouldUseLowResIcon());
364 } else {
365 // Only update the icon for restored apps.
366 si.updateIcon(mIconCache);
367 }
368 updates.add(si);
369 }
370 }
371 }
372
373 if (!updates.isEmpty()) {
374 // Push changes to the callback.
375 Runnable r = new Runnable() {
376 public void run() {
377 Callbacks callbacks = getCallback();
378 if (callbacks != null) {
379 callbacks.bindShortcutsChanged(updates,
380 new ArrayList<ShortcutInfo>(), user);
381 }
382 }
383 };
384 mHandler.post(r);
385 }
386 }
387 }
388 };
389 runOnWorkerThread(updateRunnable);
390 }
391
392 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
393 final Callbacks callbacks = getCallback();
394
395 if (allAppsApps == null) {
396 throw new RuntimeException("allAppsApps must not be null");
397 }
398 if (allAppsApps.isEmpty()) {
399 return;
400 }
401
402 // Process the newly added applications and add them to the database first
403 Runnable r = new Runnable() {
404 public void run() {
405 runOnMainThread(new Runnable() {
406 public void run() {
407 Callbacks cb = getCallback();
408 if (callbacks == cb && cb != null) {
409 callbacks.bindAppsAdded(null, null, null, allAppsApps);
410 }
411 }
412 });
413 }
414 };
415 runOnWorkerThread(r);
416 }
417
418 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
419 int[] xy, int spanX, int spanY) {
420 LauncherAppState app = LauncherAppState.getInstance();
421 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
422 final int xCount = (int) profile.numColumns;
423 final int yCount = (int) profile.numRows;
424 boolean[][] occupied = new boolean[xCount][yCount];
425 if (occupiedPos != null) {
426 for (ItemInfo r : occupiedPos) {
427 int right = r.cellX + r.spanX;
428 int bottom = r.cellY + r.spanY;
429 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
430 for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
431 occupied[x][y] = true;
432 }
433 }
434 }
435 }
436 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
437 }
438
439 /**
440 * Find a position on the screen for the given size or adds a new screen.
441 * @return screenId and the coordinates for the item.
442 */
443 @Thunk Pair<Long, int[]> findSpaceForItem(
444 Context context,
445 ArrayList<Long> workspaceScreens,
446 ArrayList<Long> addedWorkspaceScreensFinal,
447 int spanX, int spanY) {
448 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
449
450 // Use sBgItemsIdMap as all the items are already loaded.
451 assertWorkspaceLoaded();
452 synchronized (sBgLock) {
453 for (ItemInfo info : sBgItemsIdMap) {
454 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
455 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
456 if (items == null) {
457 items = new ArrayList<>();
458 screenItems.put(info.screenId, items);
459 }
460 items.add(info);
461 }
462 }
463 }
464
465 // Find appropriate space for the item.
466 long screenId = 0;
467 int[] cordinates = new int[2];
468 boolean found = false;
469
470 int screenCount = workspaceScreens.size();
471 // First check the preferred screen.
472 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
473 if (preferredScreenIndex < screenCount) {
474 screenId = workspaceScreens.get(preferredScreenIndex);
475 found = findNextAvailableIconSpaceInScreen(
476 screenItems.get(screenId), cordinates, spanX, spanY);
477 }
478
479 if (!found) {
480 // Search on any of the screens starting from the first screen.
481 for (int screen = 1; screen < screenCount; screen++) {
482 screenId = workspaceScreens.get(screen);
483 if (findNextAvailableIconSpaceInScreen(
484 screenItems.get(screenId), cordinates, spanX, spanY)) {
485 // We found a space for it
486 found = true;
487 break;
488 }
489 }
490 }
491
492 if (!found) {
493 // Still no position found. Add a new screen to the end.
494 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
495
496 // Save the screen id for binding in the workspace
497 workspaceScreens.add(screenId);
498 addedWorkspaceScreensFinal.add(screenId);
499
500 // If we still can't find an empty space, then God help us all!!!
501 if (!findNextAvailableIconSpaceInScreen(
502 screenItems.get(screenId), cordinates, spanX, spanY)) {
503 throw new RuntimeException("Can't find space to add the item");
504 }
505 }
506 return Pair.create(screenId, cordinates);
507 }
508
509 /**
510 * Adds the provided items to the workspace.
511 */
512 public void addAndBindAddedWorkspaceItems(final Context context,
513 final ArrayList<? extends ItemInfo> workspaceApps) {
514 final Callbacks callbacks = getCallback();
515 if (workspaceApps.isEmpty()) {
516 return;
517 }
518 // Process the newly added applications and add them to the database first
519 Runnable r = new Runnable() {
520 public void run() {
521 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
522 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
523
524 // Get the list of workspace screens. We need to append to this list and
525 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
526 // called.
527 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
528 synchronized(sBgLock) {
529 for (ItemInfo item : workspaceApps) {
530 if (item instanceof ShortcutInfo) {
531 // Short-circuit this logic if the icon exists somewhere on the workspace
532 if (shortcutExists(context, item.getIntent(), item.user)) {
533 continue;
534 }
535 }
536
537 // Find appropriate space for the item.
538 Pair<Long, int[]> coords = findSpaceForItem(context,
539 workspaceScreens, addedWorkspaceScreensFinal,
540 1, 1);
541 long screenId = coords.first;
542 int[] cordinates = coords.second;
543
544 ItemInfo itemInfo;
545 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
546 itemInfo = item;
547 } else if (item instanceof AppInfo) {
548 itemInfo = ((AppInfo) item).makeShortcut();
549 } else {
550 throw new RuntimeException("Unexpected info type");
551 }
552
553 // Add the shortcut to the db
554 addItemToDatabase(context, itemInfo,
555 LauncherSettings.Favorites.CONTAINER_DESKTOP,
556 screenId, cordinates[0], cordinates[1]);
557 // Save the ShortcutInfo for binding in the workspace
558 addedShortcutsFinal.add(itemInfo);
559 }
560 }
561
562 // Update the workspace screens
563 updateWorkspaceScreenOrder(context, workspaceScreens);
564
565 if (!addedShortcutsFinal.isEmpty()) {
566 runOnMainThread(new Runnable() {
567 public void run() {
568 Callbacks cb = getCallback();
569 if (callbacks == cb && cb != null) {
570 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
571 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
572 if (!addedShortcutsFinal.isEmpty()) {
573 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
574 long lastScreenId = info.screenId;
575 for (ItemInfo i : addedShortcutsFinal) {
576 if (i.screenId == lastScreenId) {
577 addAnimated.add(i);
578 } else {
579 addNotAnimated.add(i);
580 }
581 }
582 }
583 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
584 addNotAnimated, addAnimated, null);
585 }
586 }
587 });
588 }
589 }
590 };
591 runOnWorkerThread(r);
592 }
593
594 private void unbindItemInfosAndClearQueuedBindRunnables() {
595 if (sWorkerThread.getThreadId() == Process.myTid()) {
596 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
597 "main thread");
598 }
599
600 // Clear any deferred bind runnables
601 synchronized (mDeferredBindRunnables) {
602 mDeferredBindRunnables.clear();
603 }
604
605 // Remove any queued UI runnables
606 mHandler.cancelAll();
607 // Unbind all the workspace items
608 unbindWorkspaceItemsOnMainThread();
609 }
610
611 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
612 void unbindWorkspaceItemsOnMainThread() {
613 // Ensure that we don't use the same workspace items data structure on the main thread
614 // by making a copy of workspace items first.
615 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
616 synchronized (sBgLock) {
617 tmpItems.addAll(sBgWorkspaceItems);
618 tmpItems.addAll(sBgAppWidgets);
619 }
620 Runnable r = new Runnable() {
621 @Override
622 public void run() {
623 for (ItemInfo item : tmpItems) {
624 item.unbind();
625 }
626 }
627 };
628 runOnMainThread(r);
629 }
630
631 /**
632 * Adds an item to the DB if it was not created previously, or move it to a new
633 * <container, screen, cellX, cellY>
634 */
635 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
636 long screenId, int cellX, int cellY) {
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(
647 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
648 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
649 if (modelItem != null && item != modelItem) {
650 // check all the data is consistent
651 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
652 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
653 ShortcutInfo shortcut = (ShortcutInfo) item;
654 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
655 modelShortcut.intent.filterEquals(shortcut.intent) &&
656 modelShortcut.id == shortcut.id &&
657 modelShortcut.itemType == shortcut.itemType &&
658 modelShortcut.container == shortcut.container &&
659 modelShortcut.screenId == shortcut.screenId &&
660 modelShortcut.cellX == shortcut.cellX &&
661 modelShortcut.cellY == shortcut.cellY &&
662 modelShortcut.spanX == shortcut.spanX &&
663 modelShortcut.spanY == shortcut.spanY &&
664 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
665 (modelShortcut.dropPos != null &&
666 shortcut.dropPos != null &&
667 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
668 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
669 // For all intents and purposes, this is the same object
670 return;
671 }
672 }
673
674 // the modelItem needs to match up perfectly with item if our model is
675 // to be consistent with the database-- for now, just require
676 // modelItem == item or the equality check above
677 String msg = "item: " + ((item != null) ? item.toString() : "null") +
678 "modelItem: " +
679 ((modelItem != null) ? modelItem.toString() : "null") +
680 "Error: ItemInfo passed to checkItemInfo doesn't match original";
681 RuntimeException e = new RuntimeException(msg);
682 if (stackTrace != null) {
683 e.setStackTrace(stackTrace);
684 }
685 throw e;
686 }
687 }
688
689 static void checkItemInfo(final ItemInfo item) {
690 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
691 final long itemId = item.id;
692 Runnable r = new Runnable() {
693 public void run() {
694 synchronized (sBgLock) {
695 checkItemInfoLocked(itemId, item, stackTrace);
696 }
697 }
698 };
699 runOnWorkerThread(r);
700 }
701
702 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
703 final ItemInfo item, final String callingFunction) {
704 final long itemId = item.id;
705 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
706 final ContentResolver cr = context.getContentResolver();
707
708 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
709 Runnable r = new Runnable() {
710 public void run() {
711 cr.update(uri, values, null, null);
712 updateItemArrays(item, itemId, stackTrace);
713 }
714 };
715 runOnWorkerThread(r);
716 }
717
718 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
719 final ArrayList<ItemInfo> items, final String callingFunction) {
720 final ContentResolver cr = context.getContentResolver();
721
722 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
723 Runnable r = new Runnable() {
724 public void run() {
725 ArrayList<ContentProviderOperation> ops =
726 new ArrayList<ContentProviderOperation>();
727 int count = items.size();
728 for (int i = 0; i < count; i++) {
729 ItemInfo item = items.get(i);
730 final long itemId = item.id;
731 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
732 ContentValues values = valuesList.get(i);
733
734 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
735 updateItemArrays(item, itemId, stackTrace);
736
737 }
738 try {
739 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
740 } catch (Exception e) {
741 e.printStackTrace();
742 }
743 }
744 };
745 runOnWorkerThread(r);
746 }
747
748 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
749 // Lock on mBgLock *after* the db operation
750 synchronized (sBgLock) {
751 checkItemInfoLocked(itemId, item, stackTrace);
752
753 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
754 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
755 // Item is in a folder, make sure this folder exists
756 if (!sBgFolders.containsKey(item.container)) {
757 // An items container is being set to a that of an item which is not in
758 // the list of Folders.
759 String msg = "item: " + item + " container being set to: " +
760 item.container + ", not in the list of folders";
761 Log.e(TAG, msg);
762 }
763 }
764
765 // Items are added/removed from the corresponding FolderInfo elsewhere, such
766 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
767 // that are on the desktop, as appropriate
768 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
769 if (modelItem != null &&
770 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
771 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
772 switch (modelItem.itemType) {
773 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
774 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
775 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
776 if (!sBgWorkspaceItems.contains(modelItem)) {
777 sBgWorkspaceItems.add(modelItem);
778 }
779 break;
780 default:
781 break;
782 }
783 } else {
784 sBgWorkspaceItems.remove(modelItem);
785 }
786 }
787 }
788
789 /**
790 * Move an item in the DB to a new <container, screen, cellX, cellY>
791 */
792 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
793 final long screenId, final int cellX, final int cellY) {
794 item.container = container;
795 item.cellX = cellX;
796 item.cellY = cellY;
797
798 // We store hotseat items in canonical form which is this orientation invariant position
799 // in the hotseat
800 if (context instanceof Launcher && screenId < 0 &&
801 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
802 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
803 } else {
804 item.screenId = screenId;
805 }
806
807 final ContentValues values = new ContentValues();
808 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
809 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
810 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
811 values.put(LauncherSettings.Favorites.RANK, item.rank);
812 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
813
814 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
815 }
816
817 /**
818 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
819 * cellX, cellY have already been updated on the ItemInfos.
820 */
821 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
822 final long container, final int screen) {
823
824 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
825 int count = items.size();
826
827 for (int i = 0; i < count; i++) {
828 ItemInfo item = items.get(i);
829 item.container = container;
830
831 // We store hotseat items in canonical form which is this orientation invariant position
832 // in the hotseat
833 if (context instanceof Launcher && screen < 0 &&
834 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
835 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
836 item.cellY);
837 } else {
838 item.screenId = screen;
839 }
840
841 final ContentValues values = new ContentValues();
842 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
843 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
844 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
845 values.put(LauncherSettings.Favorites.RANK, item.rank);
846 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
847
848 contentValues.add(values);
849 }
850 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
851 }
852
853 /**
854 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
855 */
856 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
857 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
858 item.container = container;
859 item.cellX = cellX;
860 item.cellY = cellY;
861 item.spanX = spanX;
862 item.spanY = spanY;
863
864 // We store hotseat items in canonical form which is this orientation invariant position
865 // in the hotseat
866 if (context instanceof Launcher && screenId < 0 &&
867 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
868 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
869 } else {
870 item.screenId = screenId;
871 }
872
873 final ContentValues values = new ContentValues();
874 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
875 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
876 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
877 values.put(LauncherSettings.Favorites.RANK, item.rank);
878 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
879 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
880 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
881
882 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
883 }
884
885 /**
886 * Update an item to the database in a specified container.
887 */
888 public static void updateItemInDatabase(Context context, final ItemInfo item) {
889 final ContentValues values = new ContentValues();
890 item.onAddToDatabase(context, values);
891 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
892 }
893
894 private void assertWorkspaceLoaded() {
895 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
896 throw new RuntimeException("Trying to add shortcut while loader is running");
897 }
898 }
899
900 /**
901 * Returns true if the shortcuts already exists on the workspace. This must be called after
902 * the workspace has been loaded. We identify a shortcut by its intent.
903 */
904 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
905 assertWorkspaceLoaded();
906 final String intentWithPkg, intentWithoutPkg;
907 if (intent.getComponent() != null) {
908 // If component is not null, an intent with null package will produce
909 // the same result and should also be a match.
910 String packageName = intent.getComponent().getPackageName();
911 if (intent.getPackage() != null) {
912 intentWithPkg = intent.toUri(0);
913 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
914 } else {
915 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
916 intentWithoutPkg = intent.toUri(0);
917 }
918 } else {
919 intentWithPkg = intent.toUri(0);
920 intentWithoutPkg = intent.toUri(0);
921 }
922
923 synchronized (sBgLock) {
924 for (ItemInfo item : sBgItemsIdMap) {
925 if (item instanceof ShortcutInfo) {
926 ShortcutInfo info = (ShortcutInfo) item;
927 Intent targetIntent = info.promisedIntent == null
928 ? info.intent : info.promisedIntent;
929 if (targetIntent != null && info.user.equals(user)) {
930 String s = targetIntent.toUri(0);
931 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
932 return true;
933 }
934 }
935 }
936 }
937 }
938 return false;
939 }
940
941 /**
942 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
943 */
944 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
945 final ContentResolver cr = context.getContentResolver();
946 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
947 "_id=? and (itemType=? or itemType=?)",
948 new String[] { String.valueOf(id),
949 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
950
951 try {
952 if (c.moveToFirst()) {
953 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
954 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
955 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
956 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
957 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
958 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
959 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
960
961 FolderInfo folderInfo = null;
962 switch (c.getInt(itemTypeIndex)) {
963 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
964 folderInfo = findOrMakeFolder(folderList, id);
965 break;
966 }
967
968 // Do not trim the folder label, as is was set by the user.
969 folderInfo.title = c.getString(titleIndex);
970 folderInfo.id = id;
971 folderInfo.container = c.getInt(containerIndex);
972 folderInfo.screenId = c.getInt(screenIndex);
973 folderInfo.cellX = c.getInt(cellXIndex);
974 folderInfo.cellY = c.getInt(cellYIndex);
975 folderInfo.options = c.getInt(optionsIndex);
976
977 return folderInfo;
978 }
979 } finally {
980 c.close();
981 }
982
983 return null;
984 }
985
986 /**
987 * Add an item to the database in a specified container. Sets the container, screen, cellX and
988 * cellY fields of the item. Also assigns an ID to the item.
989 */
990 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
991 final long screenId, final int cellX, final int cellY) {
992 item.container = container;
993 item.cellX = cellX;
994 item.cellY = cellY;
995 // We store hotseat items in canonical form which is this orientation invariant position
996 // in the hotseat
997 if (context instanceof Launcher && screenId < 0 &&
998 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
999 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1000 } else {
1001 item.screenId = screenId;
1002 }
1003
1004 final ContentValues values = new ContentValues();
1005 final ContentResolver cr = context.getContentResolver();
1006 item.onAddToDatabase(context, values);
1007
1008 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1009 values.put(LauncherSettings.Favorites._ID, item.id);
1010
1011 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1012 Runnable r = new Runnable() {
1013 public void run() {
1014 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
1015
1016 // Lock on mBgLock *after* the db operation
1017 synchronized (sBgLock) {
1018 checkItemInfoLocked(item.id, item, stackTrace);
1019 sBgItemsIdMap.put(item.id, item);
1020 switch (item.itemType) {
1021 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1022 sBgFolders.put(item.id, (FolderInfo) item);
1023 // Fall through
1024 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1025 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1026 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1027 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1028 sBgWorkspaceItems.add(item);
1029 } else {
1030 if (!sBgFolders.containsKey(item.container)) {
1031 // Adding an item to a folder that doesn't exist.
1032 String msg = "adding item: " + item + " to a folder that " +
1033 " doesn't exist";
1034 Log.e(TAG, msg);
1035 }
1036 }
1037 break;
1038 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1039 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1040 break;
1041 }
1042 }
1043 }
1044 };
1045 runOnWorkerThread(r);
1046 }
1047
1048 /**
1049 * Creates a new unique child id, for a given cell span across all layouts.
1050 */
1051 static int getCellLayoutChildId(
1052 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1053 return (((int) container & 0xFF) << 24)
1054 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1055 }
1056
1057 private static ArrayList<ItemInfo> getItemsByPackageName(
1058 final String pn, final UserHandleCompat user) {
1059 ItemInfoFilter filter = new ItemInfoFilter() {
1060 @Override
1061 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1062 return cn.getPackageName().equals(pn) && info.user.equals(user);
1063 }
1064 };
1065 return filterItemInfos(sBgItemsIdMap, filter);
1066 }
1067
1068 /**
1069 * Removes all the items from the database corresponding to the specified package.
1070 */
1071 static void deletePackageFromDatabase(Context context, final String pn,
1072 final UserHandleCompat user) {
1073 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1074 }
1075
1076 /**
1077 * Removes the specified item from the database
1078 * @param context
1079 * @param item
1080 */
1081 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1082 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1083 items.add(item);
1084 deleteItemsFromDatabase(context, items);
1085 }
1086
1087 /**
1088 * Removes the specified items from the database
1089 * @param context
1090 * @param item
1091 */
1092 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1093 final ContentResolver cr = context.getContentResolver();
1094 Runnable r = new Runnable() {
1095 public void run() {
1096 for (ItemInfo item : items) {
1097 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1098 cr.delete(uri, null, null);
1099
1100 // Lock on mBgLock *after* the db operation
1101 synchronized (sBgLock) {
1102 switch (item.itemType) {
1103 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1104 sBgFolders.remove(item.id);
1105 for (ItemInfo info: sBgItemsIdMap) {
1106 if (info.container == item.id) {
1107 // We are deleting a folder which still contains items that
1108 // think they are contained by that folder.
1109 String msg = "deleting a folder (" + item + ") which still " +
1110 "contains items (" + info + ")";
1111 Log.e(TAG, msg);
1112 }
1113 }
1114 sBgWorkspaceItems.remove(item);
1115 break;
1116 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1117 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1118 sBgWorkspaceItems.remove(item);
1119 break;
1120 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1121 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1122 break;
1123 }
1124 sBgItemsIdMap.remove(item.id);
1125 }
1126 }
1127 }
1128 };
1129 runOnWorkerThread(r);
1130 }
1131
1132 /**
1133 * Update the order of the workspace screens in the database. The array list contains
1134 * a list of screen ids in the order that they should appear.
1135 */
1136 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1137 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1138 final ContentResolver cr = context.getContentResolver();
1139 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1140
1141 // Remove any negative screen ids -- these aren't persisted
1142 Iterator<Long> iter = screensCopy.iterator();
1143 while (iter.hasNext()) {
1144 long id = iter.next();
1145 if (id < 0) {
1146 iter.remove();
1147 }
1148 }
1149
1150 Runnable r = new Runnable() {
1151 @Override
1152 public void run() {
1153 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1154 // Clear the table
1155 ops.add(ContentProviderOperation.newDelete(uri).build());
1156 int count = screensCopy.size();
1157 for (int i = 0; i < count; i++) {
1158 ContentValues v = new ContentValues();
1159 long screenId = screensCopy.get(i);
1160 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1161 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1162 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1163 }
1164
1165 try {
1166 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1167 } catch (Exception ex) {
1168 throw new RuntimeException(ex);
1169 }
1170
1171 synchronized (sBgLock) {
1172 sBgWorkspaceScreens.clear();
1173 sBgWorkspaceScreens.addAll(screensCopy);
1174 }
1175 }
1176 };
1177 runOnWorkerThread(r);
1178 }
1179
1180 /**
1181 * Remove the contents of the specified folder from the database
1182 */
1183 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1184 final ContentResolver cr = context.getContentResolver();
1185
1186 Runnable r = new Runnable() {
1187 public void run() {
1188 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1189 // Lock on mBgLock *after* the db operation
1190 synchronized (sBgLock) {
1191 sBgItemsIdMap.remove(info.id);
1192 sBgFolders.remove(info.id);
1193 sBgWorkspaceItems.remove(info);
1194 }
1195
1196 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1197 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 for (ItemInfo childInfo : info.contents) {
1201 sBgItemsIdMap.remove(childInfo.id);
1202 }
1203 }
1204 }
1205 };
1206 runOnWorkerThread(r);
1207 }
1208
1209 /**
1210 * Set this as the current Launcher activity object for the loader.
1211 */
1212 public void initialize(Callbacks callbacks) {
1213 synchronized (mLock) {
1214 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1215 // workspace to prevent leaking Launcher activities on orientation change.
1216 unbindItemInfosAndClearQueuedBindRunnables();
1217 mCallbacks = new WeakReference<Callbacks>(callbacks);
1218 }
1219 }
1220
1221 @Override
1222 public void onPackageChanged(String packageName, UserHandleCompat user) {
1223 int op = PackageUpdatedTask.OP_UPDATE;
1224 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1225 user));
1226 }
1227
1228 @Override
1229 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1230 int op = PackageUpdatedTask.OP_REMOVE;
1231 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1232 user));
1233 }
1234
1235 @Override
1236 public void onPackageAdded(String packageName, UserHandleCompat user) {
1237 int op = PackageUpdatedTask.OP_ADD;
1238 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1239 user));
1240 }
1241
1242 @Override
1243 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1244 boolean replacing) {
1245 if (!replacing) {
1246 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1247 user));
1248 if (mAppsCanBeOnRemoveableStorage) {
1249 // Only rebind if we support removable storage. It catches the
1250 // case where
1251 // apps on the external sd card need to be reloaded
1252 startLoaderFromBackground();
1253 }
1254 } else {
1255 // If we are replacing then just update the packages in the list
1256 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1257 packageNames, user));
1258 }
1259 }
1260
1261 @Override
1262 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1263 boolean replacing) {
1264 if (!replacing) {
1265 enqueuePackageUpdated(new PackageUpdatedTask(
1266 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1267 user));
1268 }
1269 }
1270
1271 /**
1272 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1273 * ACTION_PACKAGE_CHANGED.
1274 */
1275 @Override
1276 public void onReceive(Context context, Intent intent) {
1277 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1278
1279 final String action = intent.getAction();
1280 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1281 // If we have changed locale we need to clear out the labels in all apps/workspace.
1282 forceReload();
1283 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1284 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1285 Callbacks callbacks = getCallback();
1286 if (callbacks != null) {
1287 callbacks.bindSearchablesChanged();
1288 }
1289 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1290 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1291 forceReload();
1292 }
1293 }
1294
1295 void forceReload() {
1296 resetLoadedState(true, true);
1297
1298 // Do this here because if the launcher activity is running it will be restarted.
1299 // If it's not running startLoaderFromBackground will merely tell it that it needs
1300 // to reload.
1301 startLoaderFromBackground();
1302 }
1303
1304 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1305 synchronized (mLock) {
1306 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1307 // mWorkspaceLoaded to true later
1308 stopLoaderLocked();
1309 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1310 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1311 }
1312 }
1313
1314 /**
1315 * When the launcher is in the background, it's possible for it to miss paired
1316 * configuration changes. So whenever we trigger the loader from the background
1317 * tell the launcher that it needs to re-run the loader when it comes back instead
1318 * of doing it now.
1319 */
1320 public void startLoaderFromBackground() {
1321 boolean runLoader = false;
1322 Callbacks callbacks = getCallback();
1323 if (callbacks != null) {
1324 // Only actually run the loader if they're not paused.
1325 if (!callbacks.setLoadOnResume()) {
1326 runLoader = true;
1327 }
1328 }
1329 if (runLoader) {
1330 startLoader(PagedView.INVALID_RESTORE_PAGE);
1331 }
1332 }
1333
1334 /**
1335 * If there is already a loader task running, tell it to stop.
1336 */
1337 private void stopLoaderLocked() {
1338 LoaderTask oldTask = mLoaderTask;
1339 if (oldTask != null) {
1340 oldTask.stopLocked();
1341 }
1342 }
1343
1344 public boolean isCurrentCallbacks(Callbacks callbacks) {
1345 return (mCallbacks != null && mCallbacks.get() == callbacks);
1346 }
1347
1348 public void startLoader(int synchronousBindPage) {
1349 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1350 }
1351
1352 public void startLoader(int synchronousBindPage, int loadFlags) {
1353 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1354 InstallShortcutReceiver.enableInstallQueue();
1355 synchronized (mLock) {
1356 // Clear any deferred bind-runnables from the synchronized load process
1357 // We must do this before any loading/binding is scheduled below.
1358 synchronized (mDeferredBindRunnables) {
1359 mDeferredBindRunnables.clear();
1360 }
1361
1362 // Don't bother to start the thread if we know it's not going to do anything
1363 if (mCallbacks != null && mCallbacks.get() != null) {
1364 // If there is already one running, tell it to stop.
1365 stopLoaderLocked();
1366 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1367 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1368 && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
1369 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1370 } else {
1371 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1372 sWorker.post(mLoaderTask);
1373 }
1374 }
1375 }
1376 }
1377
1378 void bindRemainingSynchronousPages() {
1379 // Post the remaining side pages to be loaded
1380 if (!mDeferredBindRunnables.isEmpty()) {
1381 Runnable[] deferredBindRunnables = null;
1382 synchronized (mDeferredBindRunnables) {
1383 deferredBindRunnables = mDeferredBindRunnables.toArray(
1384 new Runnable[mDeferredBindRunnables.size()]);
1385 mDeferredBindRunnables.clear();
1386 }
1387 for (final Runnable r : deferredBindRunnables) {
1388 mHandler.post(r);
1389 }
1390 }
1391
1392 // Run all the bind complete runnables after workspace is bound.
1393 if (!mBindCompleteRunnables.isEmpty()) {
1394 synchronized (mBindCompleteRunnables) {
1395 for (final Runnable r : mBindCompleteRunnables) {
1396 runOnWorkerThread(r);
1397 }
1398 mBindCompleteRunnables.clear();
1399 }
1400 }
1401 }
1402
1403 public void stopLoader() {
1404 synchronized (mLock) {
1405 if (mLoaderTask != null) {
1406 mLoaderTask.stopLocked();
1407 }
1408 }
1409 }
1410
1411 /**
1412 * Loads the workspace screen ids in an ordered list.
1413 */
1414 @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1415 final ContentResolver contentResolver = context.getContentResolver();
1416 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1417
1418 // Get screens ordered by rank.
1419 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1420 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1421 ArrayList<Long> screenIds = new ArrayList<Long>();
1422 try {
1423 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1424 while (sc.moveToNext()) {
1425 try {
1426 screenIds.add(sc.getLong(idIndex));
1427 } catch (Exception e) {
1428 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1429 + " - invalid screens: " + e, true);
1430 }
1431 }
1432 } finally {
1433 sc.close();
1434 }
1435 return screenIds;
1436 }
1437
1438 public boolean isAllAppsLoaded() {
1439 return mAllAppsLoaded;
1440 }
1441
1442 boolean isLoadingWorkspace() {
1443 synchronized (mLock) {
1444 if (mLoaderTask != null) {
1445 return mLoaderTask.isLoadingWorkspace();
1446 }
1447 }
1448 return false;
1449 }
1450
1451 /**
1452 * Runnable for the thread that loads the contents of the launcher:
1453 * - workspace icons
1454 * - widgets
1455 * - all apps icons
1456 */
1457 private class LoaderTask implements Runnable {
1458 private Context mContext;
1459 @Thunk boolean mIsLoadingAndBindingWorkspace;
1460 private boolean mStopped;
1461 @Thunk boolean mLoadAndBindStepFinished;
1462 private int mFlags;
1463
1464 LoaderTask(Context context, int flags) {
1465 mContext = context;
1466 mFlags = flags;
1467 }
1468
1469 boolean isLoadingWorkspace() {
1470 return mIsLoadingAndBindingWorkspace;
1471 }
1472
1473 private void loadAndBindWorkspace() {
1474 mIsLoadingAndBindingWorkspace = true;
1475
1476 // Load the workspace
1477 if (DEBUG_LOADERS) {
1478 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1479 }
1480
1481 if (!mWorkspaceLoaded) {
1482 loadWorkspace();
1483 synchronized (LoaderTask.this) {
1484 if (mStopped) {
1485 return;
1486 }
1487 mWorkspaceLoaded = true;
1488 }
1489 }
1490
1491 // Bind the workspace
1492 bindWorkspace(-1);
1493 }
1494
1495 private void waitForIdle() {
1496 // Wait until the either we're stopped or the other threads are done.
1497 // This way we don't start loading all apps until the workspace has settled
1498 // down.
1499 synchronized (LoaderTask.this) {
1500 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1501
1502 mHandler.postIdle(new Runnable() {
1503 public void run() {
1504 synchronized (LoaderTask.this) {
1505 mLoadAndBindStepFinished = true;
1506 if (DEBUG_LOADERS) {
1507 Log.d(TAG, "done with previous binding step");
1508 }
1509 LoaderTask.this.notify();
1510 }
1511 }
1512 });
1513
1514 while (!mStopped && !mLoadAndBindStepFinished) {
1515 try {
1516 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1517 // wait no longer than 1sec at a time
1518 this.wait(1000);
1519 } catch (InterruptedException ex) {
1520 // Ignore
1521 }
1522 }
1523 if (DEBUG_LOADERS) {
1524 Log.d(TAG, "waited "
1525 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1526 + "ms for previous step to finish binding");
1527 }
1528 }
1529 }
1530
1531 void runBindSynchronousPage(int synchronousBindPage) {
1532 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1533 // Ensure that we have a valid page index to load synchronously
1534 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1535 "valid page index");
1536 }
1537 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1538 // Ensure that we don't try and bind a specified page when the pages have not been
1539 // loaded already (we should load everything asynchronously in that case)
1540 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1541 }
1542 synchronized (mLock) {
1543 if (mIsLoaderTaskRunning) {
1544 // Ensure that we are never running the background loading at this point since
1545 // we also touch the background collections
1546 throw new RuntimeException("Error! Background loading is already running");
1547 }
1548 }
1549
1550 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1551 // data structures, we can't allow any other thread to touch that data, but because
1552 // this call is synchronous, we can get away with not locking).
1553
1554 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1555 // operations from the previous activity. We need to ensure that all queued operations
1556 // are executed before any synchronous binding work is done.
1557 mHandler.flush();
1558
1559 // Divide the set of loaded items into those that we are binding synchronously, and
1560 // everything else that is to be bound normally (asynchronously).
1561 bindWorkspace(synchronousBindPage);
1562 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1563 // arise from that.
1564 onlyBindAllApps();
1565 }
1566
1567 public void run() {
1568 synchronized (mLock) {
1569 if (mStopped) {
1570 return;
1571 }
1572 mIsLoaderTaskRunning = true;
1573 }
1574 // Optimize for end-user experience: if the Launcher is up and // running with the
1575 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1576 // workspace first (default).
1577 keep_running: {
1578 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1579 loadAndBindWorkspace();
1580
1581 if (mStopped) {
1582 break keep_running;
1583 }
1584
1585 waitForIdle();
1586
1587 // second step
1588 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1589 loadAndBindAllApps();
1590 }
1591
1592 // Clear out this reference, otherwise we end up holding it until all of the
1593 // callback runnables are done.
1594 mContext = null;
1595
1596 synchronized (mLock) {
1597 // If we are still the last one to be scheduled, remove ourselves.
1598 if (mLoaderTask == this) {
1599 mLoaderTask = null;
1600 }
1601 mIsLoaderTaskRunning = false;
1602 mHasLoaderCompletedOnce = true;
1603 }
1604 }
1605
1606 public void stopLocked() {
1607 synchronized (LoaderTask.this) {
1608 mStopped = true;
1609 this.notify();
1610 }
1611 }
1612
1613 /**
1614 * Gets the callbacks object. If we've been stopped, or if the launcher object
1615 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1616 * object that was around when the deferred message was scheduled, and if there's
1617 * a new Callbacks object around then also return null. This will save us from
1618 * calling onto it with data that will be ignored.
1619 */
1620 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1621 synchronized (mLock) {
1622 if (mStopped) {
1623 return null;
1624 }
1625
1626 if (mCallbacks == null) {
1627 return null;
1628 }
1629
1630 final Callbacks callbacks = mCallbacks.get();
1631 if (callbacks != oldCallbacks) {
1632 return null;
1633 }
1634 if (callbacks == null) {
1635 Log.w(TAG, "no mCallbacks");
1636 return null;
1637 }
1638
1639 return callbacks;
1640 }
1641 }
1642
1643 // check & update map of what's occupied; used to discard overlapping/invalid items
1644 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1645 LauncherAppState app = LauncherAppState.getInstance();
1646 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1647 final int countX = (int) profile.numColumns;
1648 final int countY = (int) profile.numRows;
1649
1650 long containerIndex = item.screenId;
1651 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1652 // Return early if we detect that an item is under the hotseat button
1653 if (mCallbacks == null ||
1654 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1655 Log.e(TAG, "Error loading shortcut into hotseat " + item
1656 + " into position (" + item.screenId + ":" + item.cellX + ","
1657 + item.cellY + ") occupied by all apps");
1658 return false;
1659 }
1660
1661 final ItemInfo[][] hotseatItems =
1662 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1663
1664 if (item.screenId >= profile.numHotseatIcons) {
1665 Log.e(TAG, "Error loading shortcut " + item
1666 + " into hotseat position " + item.screenId
1667 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1668 + ")");
1669 return false;
1670 }
1671
1672 if (hotseatItems != null) {
1673 if (hotseatItems[(int) item.screenId][0] != null) {
1674 Log.e(TAG, "Error loading shortcut into hotseat " + item
1675 + " into position (" + item.screenId + ":" + item.cellX + ","
1676 + item.cellY + ") occupied by "
1677 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1678 [(int) item.screenId][0]);
1679 return false;
1680 } else {
1681 hotseatItems[(int) item.screenId][0] = item;
1682 return true;
1683 }
1684 } else {
1685 final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
1686 items[(int) item.screenId][0] = item;
1687 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1688 return true;
1689 }
1690 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1691 // Skip further checking if it is not the hotseat or workspace container
1692 return true;
1693 }
1694
1695 if (!occupied.containsKey(item.screenId)) {
1696 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1697 occupied.put(item.screenId, items);
1698 }
1699
1700 final ItemInfo[][] screens = occupied.get(item.screenId);
1701 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1702 item.cellX < 0 || item.cellY < 0 ||
1703 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1704 Log.e(TAG, "Error loading shortcut " + item
1705 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1706 + item.cellX + "," + item.cellY
1707 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1708 return false;
1709 }
1710
1711 // Check if any workspace icons overlap with each other
1712 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1713 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1714 if (screens[x][y] != null) {
1715 Log.e(TAG, "Error loading shortcut " + item
1716 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1717 + x + "," + y
1718 + ") occupied by "
1719 + screens[x][y]);
1720 return false;
1721 }
1722 }
1723 }
1724 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1725 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1726 screens[x][y] = item;
1727 }
1728 }
1729
1730 return true;
1731 }
1732
1733 /** Clears all the sBg data structures */
1734 private void clearSBgDataStructures() {
1735 synchronized (sBgLock) {
1736 sBgWorkspaceItems.clear();
1737 sBgAppWidgets.clear();
1738 sBgFolders.clear();
1739 sBgItemsIdMap.clear();
1740 sBgWorkspaceScreens.clear();
1741 }
1742 }
1743
1744 private void loadWorkspace() {
1745 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1746
1747 final Context context = mContext;
1748 final ContentResolver contentResolver = context.getContentResolver();
1749 final PackageManager manager = context.getPackageManager();
1750 final boolean isSafeMode = manager.isSafeMode();
1751 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1752 final boolean isSdCardReady = context.registerReceiver(null,
1753 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1754
1755 LauncherAppState app = LauncherAppState.getInstance();
1756 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1757 int countX = (int) profile.numColumns;
1758 int countY = (int) profile.numRows;
1759
1760 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1761 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1762 LauncherAppState.getLauncherProvider().deleteDatabase();
1763 }
1764
1765 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1766 // append the user's Launcher2 shortcuts
1767 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1768 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1769 } else {
1770 // Make sure the default workspace is loaded
1771 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1772 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1773 }
1774
1775 synchronized (sBgLock) {
1776 clearSBgDataStructures();
1777 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1778 .getInstance(mContext).updateAndGetActiveSessionCache();
1779
1780 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1781 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1782 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1783 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1784 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1785
1786 // +1 for the hotseat (it can be larger than the workspace)
1787 // Load workspace in reverse order to ensure that latest items are loaded first (and
1788 // before any earlier duplicates)
1789 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1790
1791 try {
1792 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1793 final int intentIndex = c.getColumnIndexOrThrow
1794 (LauncherSettings.Favorites.INTENT);
1795 final int titleIndex = c.getColumnIndexOrThrow
1796 (LauncherSettings.Favorites.TITLE);
1797 final int containerIndex = c.getColumnIndexOrThrow(
1798 LauncherSettings.Favorites.CONTAINER);
1799 final int itemTypeIndex = c.getColumnIndexOrThrow(
1800 LauncherSettings.Favorites.ITEM_TYPE);
1801 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1802 LauncherSettings.Favorites.APPWIDGET_ID);
1803 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1804 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1805 final int screenIndex = c.getColumnIndexOrThrow(
1806 LauncherSettings.Favorites.SCREEN);
1807 final int cellXIndex = c.getColumnIndexOrThrow
1808 (LauncherSettings.Favorites.CELLX);
1809 final int cellYIndex = c.getColumnIndexOrThrow
1810 (LauncherSettings.Favorites.CELLY);
1811 final int spanXIndex = c.getColumnIndexOrThrow
1812 (LauncherSettings.Favorites.SPANX);
1813 final int spanYIndex = c.getColumnIndexOrThrow(
1814 LauncherSettings.Favorites.SPANY);
1815 final int rankIndex = c.getColumnIndexOrThrow(
1816 LauncherSettings.Favorites.RANK);
1817 final int restoredIndex = c.getColumnIndexOrThrow(
1818 LauncherSettings.Favorites.RESTORED);
1819 final int profileIdIndex = c.getColumnIndexOrThrow(
1820 LauncherSettings.Favorites.PROFILE_ID);
1821 final int optionsIndex = c.getColumnIndexOrThrow(
1822 LauncherSettings.Favorites.OPTIONS);
1823 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1824
1825 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1826 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1827 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1828 }
1829
1830 ShortcutInfo info;
1831 String intentDescription;
1832 LauncherAppWidgetInfo appWidgetInfo;
1833 int container;
1834 long id;
1835 long serialNumber;
1836 Intent intent;
1837 UserHandleCompat user;
1838
1839 while (!mStopped && c.moveToNext()) {
1840 try {
1841 int itemType = c.getInt(itemTypeIndex);
1842 boolean restored = 0 != c.getInt(restoredIndex);
1843 boolean allowMissingTarget = false;
1844 container = c.getInt(containerIndex);
1845
1846 switch (itemType) {
1847 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1848 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1849 id = c.getLong(idIndex);
1850 intentDescription = c.getString(intentIndex);
1851 serialNumber = c.getInt(profileIdIndex);
1852 user = allUsers.get(serialNumber);
1853 int promiseType = c.getInt(restoredIndex);
1854 int disabledState = 0;
1855 boolean itemReplaced = false;
1856 if (user == null) {
1857 // User has been deleted remove the item.
1858 itemsToRemove.add(id);
1859 continue;
1860 }
1861 try {
1862 intent = Intent.parseUri(intentDescription, 0);
1863 ComponentName cn = intent.getComponent();
1864 if (cn != null && cn.getPackageName() != null) {
1865 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1866 cn.getPackageName(), user);
1867 boolean validComponent = validPkg &&
1868 launcherApps.isActivityEnabledForProfile(cn, user);
1869
1870 if (validComponent) {
1871 if (restored) {
1872 // no special handling necessary for this item
1873 restoredRows.add(id);
1874 restored = false;
1875 }
1876 } else if (validPkg) {
1877 intent = null;
1878 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1879 // We allow auto install apps to have their intent
1880 // updated after an install.
1881 intent = manager.getLaunchIntentForPackage(
1882 cn.getPackageName());
1883 if (intent != null) {
1884 ContentValues values = new ContentValues();
1885 values.put(LauncherSettings.Favorites.INTENT,
1886 intent.toUri(0));
1887 updateItem(id, values);
1888 }
1889 }
1890
1891 if (intent == null) {
1892 // The app is installed but the component is no
1893 // longer available.
1894 Launcher.addDumpLog(TAG,
1895 "Invalid component removed: " + cn, true);
1896 itemsToRemove.add(id);
1897 continue;
1898 } else {
1899 // no special handling necessary for this item
1900 restoredRows.add(id);
1901 restored = false;
1902 }
1903 } else if (restored) {
1904 // Package is not yet available but might be
1905 // installed later.
1906 Launcher.addDumpLog(TAG,
1907 "package not yet restored: " + cn, true);
1908
1909 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1910 // Restore has started once.
1911 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1912 // App restore has started. Update the flag
1913 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1914 ContentValues values = new ContentValues();
1915 values.put(LauncherSettings.Favorites.RESTORED,
1916 promiseType);
1917 updateItem(id, values);
1918 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1919 // This is a common app. Try to replace this.
1920 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1921 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1922 if (parser.findDefaultApp()) {
1923 // Default app found. Replace it.
1924 intent = parser.parsedIntent;
1925 cn = intent.getComponent();
1926 ContentValues values = parser.parsedValues;
1927 values.put(LauncherSettings.Favorites.RESTORED, 0);
1928 updateItem(id, values);
1929 restored = false;
1930 itemReplaced = true;
1931
1932 } else if (REMOVE_UNRESTORED_ICONS) {
1933 Launcher.addDumpLog(TAG,
1934 "Unrestored package removed: " + cn, true);
1935 itemsToRemove.add(id);
1936 continue;
1937 }
1938 } else if (REMOVE_UNRESTORED_ICONS) {
1939 Launcher.addDumpLog(TAG,
1940 "Unrestored package removed: " + cn, true);
1941 itemsToRemove.add(id);
1942 continue;
1943 }
1944 } else if (launcherApps.isAppEnabled(
1945 manager, cn.getPackageName(),
1946 PackageManager.GET_UNINSTALLED_PACKAGES)) {
1947 // Package is present but not available.
1948 allowMissingTarget = true;
1949 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1950 } else if (!isSdCardReady) {
1951 // SdCard is not ready yet. Package might get available,
1952 // once it is ready.
1953 Launcher.addDumpLog(TAG, "Invalid package: " + cn
1954 + " (check again later)", true);
1955 HashSet<String> pkgs = sPendingPackages.get(user);
1956 if (pkgs == null) {
1957 pkgs = new HashSet<String>();
1958 sPendingPackages.put(user, pkgs);
1959 }
1960 pkgs.add(cn.getPackageName());
1961 allowMissingTarget = true;
1962 // Add the icon on the workspace anyway.
1963
1964 } else {
1965 // Do not wait for external media load anymore.
1966 // Log the invalid package, and remove it
1967 Launcher.addDumpLog(TAG,
1968 "Invalid package removed: " + cn, true);
1969 itemsToRemove.add(id);
1970 continue;
1971 }
1972 } else if (cn == null) {
1973 // For shortcuts with no component, keep them as they are
1974 restoredRows.add(id);
1975 restored = false;
1976 }
1977 } catch (URISyntaxException e) {
1978 Launcher.addDumpLog(TAG,
1979 "Invalid uri: " + intentDescription, true);
1980 continue;
1981 }
1982
1983 boolean useLowResIcon = container >= 0 &&
1984 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1985
1986 if (itemReplaced) {
1987 if (user.equals(UserHandleCompat.myUserHandle())) {
1988 info = getAppShortcutInfo(manager, intent, user, context, null,
1989 cursorIconInfo.iconIndex, titleIndex,
1990 false, useLowResIcon);
1991 } else {
1992 // Don't replace items for other profiles.
1993 itemsToRemove.add(id);
1994 continue;
1995 }
1996 } else if (restored) {
1997 if (user.equals(UserHandleCompat.myUserHandle())) {
1998 Launcher.addDumpLog(TAG,
1999 "constructing info for partially restored package",
2000 true);
2001 info = getRestoredItemInfo(c, titleIndex, intent,
2002 promiseType, itemType, cursorIconInfo, context);
2003 intent = getRestoredItemIntent(c, context, intent);
2004 } else {
2005 // Don't restore items for other profiles.
2006 itemsToRemove.add(id);
2007 continue;
2008 }
2009 } else if (itemType ==
2010 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2011 info = getAppShortcutInfo(manager, intent, user, context, c,
2012 cursorIconInfo.iconIndex, titleIndex,
2013 allowMissingTarget, useLowResIcon);
2014 } else {
2015 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
2016
2017 // App shortcuts that used to be automatically added to Launcher
2018 // didn't always have the correct intent flags set, so do that
2019 // here
2020 if (intent.getAction() != null &&
2021 intent.getCategories() != null &&
2022 intent.getAction().equals(Intent.ACTION_MAIN) &&
2023 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2024 intent.addFlags(
2025 Intent.FLAG_ACTIVITY_NEW_TASK |
2026 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2027 }
2028 }
2029
2030 if (info != null) {
2031 info.id = id;
2032 info.intent = intent;
2033 info.container = container;
2034 info.screenId = c.getInt(screenIndex);
2035 info.cellX = c.getInt(cellXIndex);
2036 info.cellY = c.getInt(cellYIndex);
2037 info.rank = c.getInt(rankIndex);
2038 info.spanX = 1;
2039 info.spanY = 1;
2040 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2041 if (info.promisedIntent != null) {
2042 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber🔵
2043 }
2044 info.isDisabled = disabledState;
2045 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2046 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2047 }
2048
2049 // check & update map of what's occupied
2050 if (!checkItemPlacement(occupied, info)) {
2051 itemsToRemove.add(id);
2052 break;
2053 }
2054
2055 if (restored) {
2056 ComponentName cn = info.getTargetComponent();
2057 if (cn != null) {
2058 Integer progress = installingPkgs.get(cn.getPackageName());
2059 if (progress != null) {
2060 info.setInstallProgress(progress);
2061 } else {
2062 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2063 }
2064 }
2065 }
2066
2067 switch (container) {
2068 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2069 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2070 sBgWorkspaceItems.add(info);
2071 break;
2072 default:
2073 // Item is in a user folder
2074 FolderInfo folderInfo =
2075 findOrMakeFolder(sBgFolders, container);
2076 folderInfo.add(info);
2077 break;
2078 }
2079 sBgItemsIdMap.put(info.id, info);
2080 } else {
2081 throw new RuntimeException("Unexpected null ShortcutInfo");
2082 }
2083 break;
2084
2085 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2086 id = c.getLong(idIndex);
2087 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2088
2089 // Do not trim the folder label, as is was set by the user.
2090 folderInfo.title = c.getString(titleIndex);
2091 folderInfo.id = id;
2092 folderInfo.container = container;
2093 folderInfo.screenId = c.getInt(screenIndex);
2094 folderInfo.cellX = c.getInt(cellXIndex);
2095 folderInfo.cellY = c.getInt(cellYIndex);
2096 folderInfo.spanX = 1;
2097 folderInfo.spanY = 1;
2098 folderInfo.options = c.getInt(optionsIndex);
2099
2100 // check & update map of what's occupied
2101 if (!checkItemPlacement(occupied, folderInfo)) {
2102 itemsToRemove.add(id);
2103 break;
2104 }
2105
2106 switch (container) {
2107 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2108 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2109 sBgWorkspaceItems.add(folderInfo);
2110 break;
2111 }
2112
2113 if (restored) {
2114 // no special handling required for restored folders
2115 restoredRows.add(id);
2116 }
2117
2118 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2119 sBgFolders.put(folderInfo.id, folderInfo);
2120 break;
2121
2122 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2123 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2124 // Read all Launcher-specific widget details
2125 boolean customWidget = itemType ==
2126 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2127
2128 int appWidgetId = c.getInt(appWidgetIdIndex);
2129 serialNumber= c.getLong(profileIdIndex);
2130 user = mUserManager.getUserForSerialNumber(serialNumber);
2131 if (user == null) {
2132 // User has been deleted remove the item.
2133 itemsToRemove.add(id);
2134 continue;
2135 }
2136 String savedProvider = c.getString(appWidgetProviderIndex);
2137 id = c.getLong(idIndex);
2138 user = allUsers.get(serialNumber);
2139 if (user == null) {
2140 itemsToRemove.add(id);
2141 continue;
2142 }
2143
2144 final ComponentName component =
2145 ComponentName.unflattenFromString(savedProvider);
2146
2147 final int restoreStatus = c.getInt(restoredIndex);
2148 final boolean isIdValid = (restoreStatus &
2149 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2150 final boolean wasProviderReady = (restoreStatus &
2151 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2152
2153 final LauncherAppWidgetProviderInfo provider =
2154 LauncherModel.getProviderInfo(context,
2155 ComponentName.unflattenFromString(savedProvider),
2156 user);
2157
2158 final boolean isProviderReady = isValidProvider(provider);
2159 if (!isSafeMode && !customWidget &&
2160 wasProviderReady && !isProviderReady) {
2161 String log = "Deleting widget that isn't installed anymore: "
2162 + "id=" + id + " appWidgetId=" + appWidgetId;
2163
2164 Log.e(TAG, log);
2165 Launcher.addDumpLog(TAG, log, false);
2166 itemsToRemove.add(id);
2167 } else {
2168 if (isProviderReady) {
2169 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2170 provider.provider);
2171
2172 int status = restoreStatus;
2173 if (!wasProviderReady) {
2174 // If provider was not previously ready, update the
2175 // status and UI flag.
2176
2177 // Id would be valid only if the widget restore broadcast was🔵
2178 if (isIdValid) {
2179 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2180 } else {
2181 status &= ~LauncherAppWidgetInfo
2182 .FLAG_PROVIDER_NOT_READY;
2183 }
2184 }
2185 appWidgetInfo.restoreStatus = status;
2186 } else {
2187 Log.v(TAG, "Widget restore pending id=" + id
2188 + " appWidgetId=" + appWidgetId
2189 + " status =" + restoreStatus);
2190 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2191 component);
2192 appWidgetInfo.restoreStatus = restoreStatus;
2193 Integer installProgress = installingPkgs.get(component.getPackage🔵
2194
2195 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2196 // Restore has started once.
2197 } else if (installProgress != null) {
2198 // App restore has started. Update the flag
2199 appWidgetInfo.restoreStatus |=
2200 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2201 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2202 Launcher.addDumpLog(TAG,
2203 "Unrestored widget removed: " + component, true);
2204 itemsToRemove.add(id);
2205 continue;
2206 }
2207
2208 appWidgetInfo.installProgress =
2209 installProgress == null ? 0 : installProgress;
2210 }
2211
2212 appWidgetInfo.id = id;
2213 appWidgetInfo.screenId = c.getInt(screenIndex);
2214 appWidgetInfo.cellX = c.getInt(cellXIndex);
2215 appWidgetInfo.cellY = c.getInt(cellYIndex);
2216 appWidgetInfo.spanX = c.getInt(spanXIndex);
2217 appWidgetInfo.spanY = c.getInt(spanYIndex);
2218 appWidgetInfo.user = user;
2219
2220 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2221 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2222 Log.e(TAG, "Widget found where container != " +
2223 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2224 continue;
2225 }
2226
2227 appWidgetInfo.container = container;
2228 // check & update map of what's occupied
2229 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2230 itemsToRemove.add(id);
2231 break;
2232 }
2233
2234 if (!customWidget) {
2235 String providerName =
2236 appWidgetInfo.providerName.flattenToString();
2237 if (!providerName.equals(savedProvider) ||
2238 (appWidgetInfo.restoreStatus != restoreStatus)) {
2239 ContentValues values = new ContentValues();
2240 values.put(
2241 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2242 providerName);
2243 values.put(LauncherSettings.Favorites.RESTORED,
2244 appWidgetInfo.restoreStatus);
2245 updateItem(id, values);
2246 }
2247 }
2248 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2249 sBgAppWidgets.add(appWidgetInfo);
2250 }
2251 break;
2252 }
2253 } catch (Exception e) {
2254 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2255 }
2256 }
2257 } finally {
2258 if (c != null) {
2259 c.close();
2260 }
2261 }
2262
2263 // Break early if we've stopped loading
2264 if (mStopped) {
2265 clearSBgDataStructures();
2266 return;
2267 }
2268
2269 if (itemsToRemove.size() > 0) {
2270 // Remove dead items
2271 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2272 Utilities.createDbSelectionQuery(
2273 LauncherSettings.Favorites._ID, itemsToRemove), null);
2274 if (DEBUG_LOADERS) {
2275 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2276 LauncherSettings.Favorites._ID, itemsToRemove));
2277 }
2278
2279 // Remove any empty folder
2280 for (long folderId : LauncherAppState.getLauncherProvider()
2281 .deleteEmptyFolders()) {
2282 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2283 sBgFolders.remove(folderId);
2284 sBgItemsIdMap.remove(folderId);
2285 }
2286 }
2287
2288 if (restoredRows.size() > 0) {
2289 // Update restored items that no longer require special handling
2290 ContentValues values = new ContentValues();
2291 values.put(LauncherSettings.Favorites.RESTORED, 0);
2292 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2293 Utilities.createDbSelectionQuery(
2294 LauncherSettings.Favorites._ID, restoredRows), null);
2295 }
2296
2297 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2298 context.registerReceiver(new AppsAvailabilityCheck(),
2299 new IntentFilter(StartupReceiver.SYSTEM_READY),
2300 null, sWorker);
2301 }
2302
2303 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2304
2305 // Remove any empty screens
2306 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2307 for (ItemInfo item: sBgItemsIdMap) {
2308 long screenId = item.screenId;
2309 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2310 unusedScreens.contains(screenId)) {
2311 unusedScreens.remove(screenId);
2312 }
2313 }
2314
2315 // If there are any empty screens remove them, and update.
2316 if (unusedScreens.size() != 0) {
2317 sBgWorkspaceScreens.removeAll(unusedScreens);
2318 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2319 }
2320
2321 if (DEBUG_LOADERS) {
2322 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2323 Log.d(TAG, "workspace layout: ");
2324 int nScreens = occupied.size();
2325 for (int y = 0; y < countY; y++) {
2326 String line = "";
2327
2328 for (int i = 0; i < nScreens; i++) {
2329 long screenId = occupied.keyAt(i);
2330 if (screenId > 0) {
2331 line += " | ";
2332 }
2333 ItemInfo[][] screen = occupied.valueAt(i);
2334 for (int x = 0; x < countX; x++) {
2335 if (x < screen.length && y < screen[x].length) {
2336 line += (screen[x][y] != null) ? "#" : ".";
2337 } else {
2338 line += "!";
2339 }
2340 }
2341 }
2342 Log.d(TAG, "[ " + line + " ]");
2343 }
2344 }
2345 }
2346 }
2347
2348 /**
2349 * Partially updates the item without any notification. Must be called on the worker thread.
2350 */
2351 private void updateItem(long itemId, ContentValues update) {
2352 mContext.getContentResolver().update(
2353 LauncherSettings.Favorites.CONTENT_URI,
2354 update,
2355 BaseColumns._ID + "= ?",
2356 new String[]{Long.toString(itemId)});
2357 }
2358
2359 /** Filters the set of items who are directly or indirectly (via another container) on the
2360 * specified screen. */
2361 private void filterCurrentWorkspaceItems(long currentScreenId,
2362 ArrayList<ItemInfo> allWorkspaceItems,
2363 ArrayList<ItemInfo> currentScreenItems,
2364 ArrayList<ItemInfo> otherScreenItems) {
2365 // Purge any null ItemInfos
2366 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2367 while (iter.hasNext()) {
2368 ItemInfo i = iter.next();
2369 if (i == null) {
2370 iter.remove();
2371 }
2372 }
2373
2374 // Order the set of items by their containers first, this allows use to walk through the
2375 // list sequentially, build up a list of containers that are in the specified screen,
2376 // as well as all items in those containers.
2377 Set<Long> itemsOnScreen = new HashSet<Long>();
2378 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2379 @Override
2380 public int compare(ItemInfo lhs, ItemInfo rhs) {
2381 return (int) (lhs.container - rhs.container);
2382 }
2383 });
2384 for (ItemInfo info : allWorkspaceItems) {
2385 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2386 if (info.screenId == currentScreenId) {
2387 currentScreenItems.add(info);
2388 itemsOnScreen.add(info.id);
2389 } else {
2390 otherScreenItems.add(info);
2391 }
2392 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2393 currentScreenItems.add(info);
2394 itemsOnScreen.add(info.id);
2395 } else {
2396 if (itemsOnScreen.contains(info.container)) {
2397 currentScreenItems.add(info);
2398 itemsOnScreen.add(info.id);
2399 } else {
2400 otherScreenItems.add(info);
2401 }
2402 }
2403 }
2404 }
2405
2406 /** Filters the set of widgets which are on the specified screen. */
2407 private void filterCurrentAppWidgets(long currentScreenId,
2408 ArrayList<LauncherAppWidgetInfo> appWidgets,
2409 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2410 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2411
2412 for (LauncherAppWidgetInfo widget : appWidgets) {
2413 if (widget == null) continue;
2414 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2415 widget.screenId == currentScreenId) {
2416 currentScreenWidgets.add(widget);
2417 } else {
2418 otherScreenWidgets.add(widget);
2419 }
2420 }
2421 }
2422
2423 /** Filters the set of folders which are on the specified screen. */
2424 private void filterCurrentFolders(long currentScreenId,
2425 LongArrayMap<ItemInfo> itemsIdMap,
2426 LongArrayMap<FolderInfo> folders,
2427 LongArrayMap<FolderInfo> currentScreenFolders,
2428 LongArrayMap<FolderInfo> otherScreenFolders) {
2429
2430 int total = folders.size();
2431 for (int i = 0; i < total; i++) {
2432 long id = folders.keyAt(i);
2433 FolderInfo folder = folders.valueAt(i);
2434
2435 ItemInfo info = itemsIdMap.get(id);
2436 if (info == null || folder == null) continue;
2437 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2438 info.screenId == currentScreenId) {
2439 currentScreenFolders.put(id, folder);
2440 } else {
2441 otherScreenFolders.put(id, folder);
2442 }
2443 }
2444 }
2445
2446 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2447 * right) */
2448 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2449 final LauncherAppState app = LauncherAppState.getInstance();
2450 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2451 // XXX: review this
2452 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2453 @Override
2454 public int compare(ItemInfo lhs, ItemInfo rhs) {
2455 int cellCountX = (int) profile.numColumns;
2456 int cellCountY = (int) profile.numRows;
2457 int screenOffset = cellCountX * cellCountY;
2458 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2459 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2460 lhs.cellY * cellCountX + lhs.cellX);
2461 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2462 rhs.cellY * cellCountX + rhs.cellX);
2463 return (int) (lr - rr);
2464 }
2465 });
2466 }
2467
2468 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2469 final ArrayList<Long> orderedScreens) {
2470 final Runnable r = new Runnable() {
2471 @Override
2472 public void run() {
2473 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2474 if (callbacks != null) {
2475 callbacks.bindScreens(orderedScreens);
2476 }
2477 }
2478 };
2479 runOnMainThread(r);
2480 }
2481
2482 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2483 final ArrayList<ItemInfo> workspaceItems,
2484 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2485 final LongArrayMap<FolderInfo> folders,
2486 ArrayList<Runnable> deferredBindRunnables) {
2487
2488 final boolean postOnMainThread = (deferredBindRunnables != null);
2489
2490 // Bind the workspace items
2491 int N = workspaceItems.size();
2492 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2493 final int start = i;
2494 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2495 final Runnable r = new Runnable() {
2496 @Override
2497 public void run() {
2498 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2499 if (callbacks != null) {
2500 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2501 false);
2502 }
2503 }
2504 };
2505 if (postOnMainThread) {
2506 synchronized (deferredBindRunnables) {
2507 deferredBindRunnables.add(r);
2508 }
2509 } else {
2510 runOnMainThread(r);
2511 }
2512 }
2513
2514 // Bind the folders
2515 if (!folders.isEmpty()) {
2516 final Runnable r = new Runnable() {
2517 public void run() {
2518 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2519 if (callbacks != null) {
2520 callbacks.bindFolders(folders);
2521 }
2522 }
2523 };
2524 if (postOnMainThread) {
2525 synchronized (deferredBindRunnables) {
2526 deferredBindRunnables.add(r);
2527 }
2528 } else {
2529 runOnMainThread(r);
2530 }
2531 }
2532
2533 // Bind the widgets, one at a time
2534 N = appWidgets.size();
2535 for (int i = 0; i < N; i++) {
2536 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2537 final Runnable r = new Runnable() {
2538 public void run() {
2539 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2540 if (callbacks != null) {
2541 callbacks.bindAppWidget(widget);
2542 }
2543 }
2544 };
2545 if (postOnMainThread) {
2546 deferredBindRunnables.add(r);
2547 } else {
2548 runOnMainThread(r);
2549 }
2550 }
2551 }
2552
2553 /**
2554 * Binds all loaded data to actual views on the main thread.
2555 */
2556 private void bindWorkspace(int synchronizeBindPage) {
2557 final long t = SystemClock.uptimeMillis();
2558 Runnable r;
2559
2560 // Don't use these two variables in any of the callback runnables.
2561 // Otherwise we hold a reference to them.
2562 final Callbacks oldCallbacks = mCallbacks.get();
2563 if (oldCallbacks == null) {
2564 // This launcher has exited and nobody bothered to tell us. Just bail.
2565 Log.w(TAG, "LoaderTask running with no launcher");
2566 return;
2567 }
2568
2569 // Save a copy of all the bg-thread collections
2570 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2571 ArrayList<LauncherAppWidgetInfo> appWidgets =
2572 new ArrayList<LauncherAppWidgetInfo>();
2573 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2574
2575 final LongArrayMap<FolderInfo> folders;
2576 final LongArrayMap<ItemInfo> itemsIdMap;
2577
2578 synchronized (sBgLock) {
2579 workspaceItems.addAll(sBgWorkspaceItems);
2580 appWidgets.addAll(sBgAppWidgets);
2581 orderedScreenIds.addAll(sBgWorkspaceScreens);
2582
2583 folders = sBgFolders.clone();
2584 itemsIdMap = sBgItemsIdMap.clone();
2585 }
2586
2587 final boolean isLoadingSynchronously =
2588 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2589 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2590 oldCallbacks.getCurrentWorkspaceScreen();
2591 if (currScreen >= orderedScreenIds.size()) {
2592 // There may be no workspace screens (just hotseat items and an empty page).
2593 currScreen = PagedView.INVALID_RESTORE_PAGE;
2594 }
2595 final int currentScreen = currScreen;
2596 final long currentScreenId = currentScreen < 0
2597 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2598
2599 // Load all the items that are on the current page first (and in the process, unbind
2600 // all the existing workspace items before we call startBinding() below.
2601 unbindWorkspaceItemsOnMainThread();
2602
2603 // Separate the items that are on the current screen, and all the other remaining items
2604 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2605 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2606 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2607 new ArrayList<LauncherAppWidgetInfo>();
2608 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2609 new ArrayList<LauncherAppWidgetInfo>();
2610 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2611 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2612
2613 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2614 otherWorkspaceItems);
2615 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2616 otherAppWidgets);
2617 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2618 otherFolders);
2619 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2620 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2621
2622 // Tell the workspace that we're about to start binding items
2623 r = new Runnable() {
2624 public void run() {
2625 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2626 if (callbacks != null) {
2627 callbacks.startBinding();
2628 }
2629 }
2630 };
2631 runOnMainThread(r);
2632
2633 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2634
2635 // Load items on the current page
2636 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2637 currentFolders, null);
2638 if (isLoadingSynchronously) {
2639 r = new Runnable() {
2640 public void run() {
2641 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2642 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2643 callbacks.onPageBoundSynchronously(currentScreen);
2644 }
2645 }
2646 };
2647 runOnMainThread(r);
2648 }
2649
2650 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2651 // work until after the first render)
2652 synchronized (mDeferredBindRunnables) {
2653 mDeferredBindRunnables.clear();
2654 }
2655 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2656 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2657
2658 // Tell the workspace that we're done binding items
2659 r = new Runnable() {
2660 public void run() {
2661 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2662 if (callbacks != null) {
2663 callbacks.finishBindingItems();
2664 }
2665
2666 // If we're profiling, ensure this is the last thing in the queue.
2667 if (DEBUG_LOADERS) {
2668 Log.d(TAG, "bound workspace in "
2669 + (SystemClock.uptimeMillis()-t) + "ms");
2670 }
2671
2672 mIsLoadingAndBindingWorkspace = false;
2673 }
2674 };
2675 if (isLoadingSynchronously) {
2676 synchronized (mDeferredBindRunnables) {
2677 mDeferredBindRunnables.add(r);
2678 }
2679 } else {
2680 runOnMainThread(r);
2681 }
2682 }
2683
2684 private void loadAndBindAllApps() {
2685 if (DEBUG_LOADERS) {
2686 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2687 }
2688 if (!mAllAppsLoaded) {
2689 loadAllApps();
2690 synchronized (LoaderTask.this) {
2691 if (mStopped) {
2692 return;
2693 }
2694 }
2695 updateIconCache();
2696 synchronized (LoaderTask.this) {
2697 if (mStopped) {
2698 return;
2699 }
2700 mAllAppsLoaded = true;
2701 }
2702 } else {
2703 onlyBindAllApps();
2704 }
2705 }
2706
2707 private void updateIconCache() {
2708 // Ignore packages which have a promise icon.
2709 HashSet<String> packagesToIgnore = new HashSet<>();
2710 synchronized (sBgLock) {
2711 for (ItemInfo info : sBgItemsIdMap) {
2712 if (info instanceof ShortcutInfo) {
2713 ShortcutInfo si = (ShortcutInfo) info;
2714 if (si.isPromise() && si.getTargetComponent() != null) {
2715 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2716 }
2717 } else if (info instanceof LauncherAppWidgetInfo) {
2718 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2719 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2720 packagesToIgnore.add(lawi.providerName.getPackageName());
2721 }
2722 }
2723 }
2724 }
2725 mIconCache.updateDbIcons(packagesToIgnore);
2726 }
2727
2728 private void onlyBindAllApps() {
2729 final Callbacks oldCallbacks = mCallbacks.get();
2730 if (oldCallbacks == null) {
2731 // This launcher has exited and nobody bothered to tell us. Just bail.
2732 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2733 return;
2734 }
2735
2736 // shallow copy
2737 @SuppressWarnings("unchecked")
2738 final ArrayList<AppInfo> list
2739 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2740 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2741 Runnable r = new Runnable() {
2742 public void run() {
2743 final long t = SystemClock.uptimeMillis();
2744 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2745 if (callbacks != null) {
2746 callbacks.bindAllApplications(list);
2747 callbacks.bindAllPackages(widgetList);
2748 }
2749 if (DEBUG_LOADERS) {
2750 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2751 + (SystemClock.uptimeMillis()-t) + "ms");
2752 }
2753 }
2754 };
2755 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2756 if (isRunningOnMainThread) {
2757 r.run();
2758 } else {
2759 mHandler.post(r);
2760 }
2761 }
2762
2763 private void loadAllApps() {
2764 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2765
2766 final Callbacks oldCallbacks = mCallbacks.get();
2767 if (oldCallbacks == null) {
2768 // This launcher has exited and nobody bothered to tell us. Just bail.
2769 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2770 return;
2771 }
2772
2773 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2774
2775 // Clear the list of apps
2776 mBgAllAppsList.clear();
2777 for (UserHandleCompat user : profiles) {
2778 // Query for the set of apps
2779 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2780 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2781 if (DEBUG_LOADERS) {
2782 Log.d(TAG, "getActivityList took "
2783 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2784 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2785 }
2786 // Fail if we don't have any apps
2787 // TODO: Fix this. Only fail for the current user.
2788 if (apps == null || apps.isEmpty()) {
2789 return;
2790 }
2791
2792 // Create the ApplicationInfos
2793 for (int i = 0; i < apps.size(); i++) {
2794 LauncherActivityInfoCompat app = apps.get(i);
2795 // This builds the icon bitmaps.
2796 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2797 }
2798
2799 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2800 if (heuristic != null) {
2801 runAfterBindCompletes(new Runnable() {
2802
2803 @Override
2804 public void run() {
2805 heuristic.processUserApps(apps);
2806 }
2807 });
2808 }
2809 }
2810 // Huh? Shouldn't this be inside the Runnable below?
2811 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2812 mBgAllAppsList.added = new ArrayList<AppInfo>();
2813
2814 // Post callback on main thread
2815 mHandler.post(new Runnable() {
2816 public void run() {
2817
2818 final long bindTime = SystemClock.uptimeMillis();
2819 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2820 if (callbacks != null) {
2821 callbacks.bindAllApplications(added);
2822 if (DEBUG_LOADERS) {
2823 Log.d(TAG, "bound " + added.size() + " apps in "
2824 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2825 }
2826 } else {
2827 Log.i(TAG, "not binding apps: no Launcher activity");
2828 }
2829 }
2830 });
2831 // Cleanup any data stored for a deleted user.
2832 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2833
2834 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
2835 true /* refresh */);
2836 if (DEBUG_LOADERS) {
2837 Log.d(TAG, "Icons processed in "
2838 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2839 }
2840 }
2841
2842 public void dumpState() {
2843 synchronized (sBgLock) {
2844 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2845 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2846 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2847 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2848 }
2849 }
2850 }
2851
2852 /**
2853 * Called when the icons for packages have been updated in the icon cache.
2854 */
2855 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2856 final Callbacks callbacks = getCallback();
2857 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2858 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2859
2860 // If any package icon has changed (app was updated while launcher was dead),
2861 // update the corresponding shortcuts.
2862 synchronized (sBgLock) {
2863 for (ItemInfo info : sBgItemsIdMap) {
2864 if (info instanceof ShortcutInfo && user.equals(info.user)
2865 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2866 ShortcutInfo si = (ShortcutInfo) info;
2867 ComponentName cn = si.getTargetComponent();
2868 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2869 si.updateIcon(mIconCache);
2870 updatedShortcuts.add(si);
2871 }
2872 }
2873 }
2874 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2875 }
2876
2877 if (!updatedShortcuts.isEmpty()) {
2878 final UserHandleCompat userFinal = user;
2879 mHandler.post(new Runnable() {
2880
2881 public void run() {
2882 Callbacks cb = getCallback();
2883 if (cb != null && callbacks == cb) {
2884 cb.bindShortcutsChanged(updatedShortcuts,
2885 new ArrayList<ShortcutInfo>(), userFinal);
2886 }
2887 }
2888 });
2889 }
2890
2891 if (!updatedApps.isEmpty()) {
2892 mHandler.post(new Runnable() {
2893
2894 public void run() {
2895 Callbacks cb = getCallback();
2896 if (cb != null && callbacks == cb) {
2897 cb.bindAppsUpdated(updatedApps);
2898 }
2899 }
2900 });
2901 }
2902
2903 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2904 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
2905 }
2906
2907 void enqueuePackageUpdated(PackageUpdatedTask task) {
2908 sWorker.post(task);
2909 }
2910
2911 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
2912
2913 @Override
2914 public void onReceive(Context context, Intent intent) {
2915 synchronized (sBgLock) {
2916 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2917 .getInstance(mApp.getContext());
2918 final PackageManager manager = context.getPackageManager();
2919 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2920 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2921 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2922 UserHandleCompat user = entry.getKey();
2923 packagesRemoved.clear();
2924 packagesUnavailable.clear();
2925 for (String pkg : entry.getValue()) {
2926 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2927 boolean packageOnSdcard = launcherApps.isAppEnabled(
2928 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2929 if (packageOnSdcard) {
2930 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2931 packagesUnavailable.add(pkg);
2932 } else {
2933 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2934 packagesRemoved.add(pkg);
2935 }
2936 }
2937 }
2938 if (!packagesRemoved.isEmpty()) {
2939 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2940 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2941 }
2942 if (!packagesUnavailable.isEmpty()) {
2943 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2944 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2945 }
2946 }
2947 sPendingPackages.clear();
2948 }
2949 }
2950 }
2951
2952 private class PackageUpdatedTask implements Runnable {
2953 int mOp;
2954 String[] mPackages;
2955 UserHandleCompat mUser;
2956
2957 public static final int OP_NONE = 0;
2958 public static final int OP_ADD = 1;
2959 public static final int OP_UPDATE = 2;
2960 public static final int OP_REMOVE = 3; // uninstlled
2961 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2962
2963
2964 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2965 mOp = op;
2966 mPackages = packages;
2967 mUser = user;
2968 }
2969
2970 public void run() {
2971 if (!mHasLoaderCompletedOnce) {
2972 // Loader has not yet run.
2973 return;
2974 }
2975 final Context context = mApp.getContext();
2976
2977 final String[] packages = mPackages;
2978 final int N = packages.length;
2979 switch (mOp) {
2980 case OP_ADD: {
2981 for (int i=0; i<N; i++) {
2982 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2983 mIconCache.updateIconsForPkg(packages[i], mUser);
2984 mBgAllAppsList.addPackage(context, packages[i], mUser);
2985 }
2986
2987 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2988 if (heuristic != null) {
2989 heuristic.processPackageAdd(mPackages);
2990 }
2991 break;
2992 }
2993 case OP_UPDATE:
2994 for (int i=0; i<N; i++) {
2995 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2996 mIconCache.updateIconsForPkg(packages[i], mUser);
2997 mBgAllAppsList.updatePackage(context, packages[i], mUser);
2998 mApp.getWidgetCache().removePackage(packages[i], mUser);
2999 }
3000 break;
3001 case OP_REMOVE: {
3002 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3003 if (heuristic != null) {
3004 heuristic.processPackageRemoved(mPackages);
3005 }
3006 for (int i=0; i<N; i++) {
3007 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3008 mIconCache.removeIconsForPkg(packages[i], mUser);
3009 }
3010 // Fall through
3011 }
3012 case OP_UNAVAILABLE:
3013 for (int i=0; i<N; i++) {
3014 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3015 mBgAllAppsList.removePackage(packages[i], mUser);
3016 mApp.getWidgetCache().removePackage(packages[i], mUser);
3017 }
3018 break;
3019 }
3020
3021 ArrayList<AppInfo> added = null;
3022 ArrayList<AppInfo> modified = null;
3023 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3024
3025 if (mBgAllAppsList.added.size() > 0) {
3026 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3027 mBgAllAppsList.added.clear();
3028 }
3029 if (mBgAllAppsList.modified.size() > 0) {
3030 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3031 mBgAllAppsList.modified.clear();
3032 }
3033 if (mBgAllAppsList.removed.size() > 0) {
3034 removedApps.addAll(mBgAllAppsList.removed);
3035 mBgAllAppsList.removed.clear();
3036 }
3037
3038 final Callbacks callbacks = getCallback();
3039 if (callbacks == null) {
3040 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3041 return;
3042 }
3043
3044 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3045 new HashMap<ComponentName, AppInfo>();
3046
3047 if (added != null) {
3048 addAppsToAllApps(context, added);
3049 for (AppInfo ai : added) {
3050 addedOrUpdatedApps.put(ai.componentName, ai);
3051 }
3052 }
3053
3054 if (modified != null) {
3055 final ArrayList<AppInfo> modifiedFinal = modified;
3056 for (AppInfo ai : modified) {
3057 addedOrUpdatedApps.put(ai.componentName, ai);
3058 }
3059
3060 mHandler.post(new Runnable() {
3061 public void run() {
3062 Callbacks cb = getCallback();
3063 if (callbacks == cb && cb != null) {
3064 callbacks.bindAppsUpdated(modifiedFinal);
3065 }
3066 }
3067 });
3068 }
3069
3070 // Update shortcut infos
3071 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3072 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3073 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3074 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3075
3076 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3077 synchronized (sBgLock) {
3078 for (ItemInfo info : sBgItemsIdMap) {
3079 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3080 ShortcutInfo si = (ShortcutInfo) info;
3081 boolean infoUpdated = false;
3082 boolean shortcutUpdated = false;
3083
3084 // Update shortcuts which use iconResource.
3085 if ((si.iconResource != null)
3086 && packageSet.contains(si.iconResource.packageName)) {
3087 Bitmap icon = Utilities.createIconBitmap(
3088 si.iconResource.packageName,
3089 si.iconResource.resourceName, context);
3090 if (icon != null) {
3091 si.setIcon(icon);
3092 si.usingFallbackIcon = false;
3093 infoUpdated = true;
3094 }
3095 }
3096
3097 ComponentName cn = si.getTargetComponent();
3098 if (cn != null && packageSet.contains(cn.getPackageName())) {
3099 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3100
3101 if (si.isPromise()) {
3102 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3103 // Auto install icon
3104 PackageManager pm = context.getPackageManager();
3105 ResolveInfo matched = pm.resolveActivity(
3106 new Intent(Intent.ACTION_MAIN)
3107 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3108 PackageManager.MATCH_DEFAULT_ONLY);
3109 if (matched == null) {
3110 // Try to find the best match activity.
3111 Intent intent = pm.getLaunchIntentForPackage(
3112 cn.getPackageName());
3113 if (intent != null) {
3114 cn = intent.getComponent();
3115 appInfo = addedOrUpdatedApps.get(cn);
3116 }
3117
3118 if ((intent == null) || (appInfo == null)) {
3119 removedShortcuts.add(si);
3120 continue;
3121 }
3122 si.promisedIntent = intent;
3123 }
3124 }
3125
3126 // Restore the shortcut.
3127 if (appInfo != null) {
3128 si.flags = appInfo.flags;
3129 }
3130
3131 si.intent = si.promisedIntent;
3132 si.promisedIntent = null;
3133 si.status = ShortcutInfo.DEFAULT;
3134 infoUpdated = true;
3135 si.updateIcon(mIconCache);
3136 }
3137
3138 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3139 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3140 si.updateIcon(mIconCache);
3141 si.title = Utilities.trim(appInfo.title);
3142 si.contentDescription = appInfo.contentDescription;
3143 infoUpdated = true;
3144 }
3145
3146 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3147 // Since package was just updated, the target must be available now.
3148 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3149 shortcutUpdated = true;
3150 }
3151 }
3152
3153 if (infoUpdated || shortcutUpdated) {
3154 updatedShortcuts.add(si);
3155 }
3156 if (infoUpdated) {
3157 updateItemInDatabase(context, si);
3158 }
3159 } else if (info instanceof LauncherAppWidgetInfo) {
3160 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3161 if (mUser.equals(widgetInfo.user)
3162 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3163 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3164 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3165 widgets.add(widgetInfo);
3166 updateItemInDatabase(context, widgetInfo);
3167 }
3168 }
3169 }
3170 }
3171
3172 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3173 mHandler.post(new Runnable() {
3174
3175 public void run() {
3176 Callbacks cb = getCallback();
3177 if (callbacks == cb && cb != null) {
3178 callbacks.bindShortcutsChanged(
3179 updatedShortcuts, removedShortcuts, mUser);
3180 }
3181 }
3182 });
3183 if (!removedShortcuts.isEmpty()) {
3184 deleteItemsFromDatabase(context, removedShortcuts);
3185 }
3186 }
3187 if (!widgets.isEmpty()) {
3188 mHandler.post(new Runnable() {
3189 public void run() {
3190 Callbacks cb = getCallback();
3191 if (callbacks == cb && cb != null) {
3192 callbacks.bindWidgetsRestored(widgets);
3193 }
3194 }
3195 });
3196 }
3197 }
3198
3199 final ArrayList<String> removedPackageNames =
3200 new ArrayList<String>();
3201 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3202 // Mark all packages in the broadcast to be removed
3203 removedPackageNames.addAll(Arrays.asList(packages));
3204 } else if (mOp == OP_UPDATE) {
3205 // Mark disabled packages in the broadcast to be removed
3206 for (int i=0; i<N; i++) {
3207 if (isPackageDisabled(context, packages[i], mUser)) {
3208 removedPackageNames.add(packages[i]);
3209 }
3210 }
3211 }
3212
3213 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3214 final int removeReason;
3215 if (mOp == OP_UNAVAILABLE) {
3216 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3217 } else {
3218 // Remove all the components associated with this package
3219 for (String pn : removedPackageNames) {
3220 deletePackageFromDatabase(context, pn, mUser);
3221 }
3222 // Remove all the specific components
3223 for (AppInfo a : removedApps) {
3224 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3225 deleteItemsFromDatabase(context, infos);
3226 }
3227 removeReason = 0;
3228 }
3229
3230 // Remove any queued items from the install queue
3231 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3232 // Call the components-removed callback
3233 mHandler.post(new Runnable() {
3234 public void run() {
3235 Callbacks cb = getCallback();
3236 if (callbacks == cb && cb != null) {
3237 callbacks.bindComponentsRemoved(
3238 removedPackageNames, removedApps, mUser, removeReason);
3239 }
3240 }
3241 });
3242 }
3243
3244 // onProvidersChanged method (API >= 17) already refreshed the widget list
3245 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
3246
3247 // Write all the logs to disk
3248 mHandler.post(new Runnable() {
3249 public void run() {
3250 Callbacks cb = getCallback();
3251 if (callbacks == cb && cb != null) {
3252 callbacks.dumpLogsToLocalData();
3253 }
3254 }
3255 });
3256 }
3257 }
3258
3259 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
3260 boolean refresh) {
3261 ArrayList<LauncherAppWidgetProviderInfo> results =
3262 new ArrayList<LauncherAppWidgetProviderInfo>();
3263 try {
3264 synchronized (sBgLock) {
3265 if (sBgWidgetProviders == null || refresh) {
3266 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
3267 = new HashMap<>();
3268 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
3269 LauncherAppWidgetProviderInfo info;
3270
3271 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
3272 for (AppWidgetProviderInfo pInfo : widgets) {
3273 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3274 UserHandleCompat user = wm.getUser(info);
3275 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3276 }
3277
3278 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3279 for (CustomAppWidget widget : customWidgets) {
3280 info = new LauncherAppWidgetProviderInfo(context, widget);
3281 UserHandleCompat user = wm.getUser(info);
3282 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3283 }
3284 // Replace the global list at the very end, so that if there is an exception,
3285 // previously loaded provider list is used.
3286 sBgWidgetProviders = tmpWidgetProviders;
3287 }
3288 results.addAll(sBgWidgetProviders.values());
3289 return results;
3290 }
3291 } catch (Exception e) {
3292 if (e.getCause() instanceof TransactionTooLargeException) {
3293 // the returned value may be incomplete and will not be refreshed until the next
3294 // time Launcher starts.
3295 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3296 // onResume is called to refresh the widget provider list.
3297 synchronized (sBgLock) {
3298 if (sBgWidgetProviders != null) {
3299 results.addAll(sBgWidgetProviders.values());
3300 }
3301 return results;
3302 }
3303 } else {
3304 throw e;
3305 }
3306 }
3307 }
3308
3309 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3310 UserHandleCompat user) {
3311 synchronized (sBgLock) {
3312 if (sBgWidgetProviders == null) {
3313 getWidgetProviders(ctx, false /* refresh */);
3314 }
3315 return sBgWidgetProviders.get(new ComponentKey(name, user));
3316 }
3317 }
3318
3319 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
3320 final boolean refresh) {
3321
3322 runOnWorkerThread(new Runnable() {
3323 @Override
3324 public void run() {
3325 updateWidgetsModel(context, refresh);
3326 final WidgetsModel model = mBgWidgetsModel.clone();
3327
3328 mHandler.post(new Runnable() {
3329 @Override
3330 public void run() {
3331 Callbacks cb = getCallback();
3332 if (callbacks == cb && cb != null) {
3333 callbacks.bindAllPackages(model);
3334 }
3335 }
3336 });
3337 // update the Widget entries inside DB on the worker thread.
3338 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3339 model.getRawList());
3340 }
3341 });
3342 }
3343
3344 /**
3345 * Returns a list of ResolveInfos/AppWidgetInfos.
3346 *
3347 * @see #loadAndBindWidgetsAndShortcuts
3348 */
3349 @Thunk void updateWidgetsModel(Context context, boolean refresh) {
3350 PackageManager packageManager = context.getPackageManager();
3351 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3352 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3353 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3354 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3355 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3356 }
3357
3358 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3359 UserHandleCompat user) {
3360 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3361 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3362 }
3363
3364 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3365 UserHandleCompat user) {
3366 if (cn == null) {
3367 return false;
3368 }
3369 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3370 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3371 return false;
3372 }
3373 return launcherApps.isActivityEnabledForProfile(cn, user);
3374 }
3375
3376 public static boolean isValidPackage(Context context, String packageName,
3377 UserHandleCompat user) {
3378 if (packageName == null) {
3379 return false;
3380 }
3381 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3382 return launcherApps.isPackageEnabledForProfile(packageName, user);
3383 }
3384
3385 /**
3386 * Make an ShortcutInfo object for a restored application or shortcut item that points
3387 * to a package that is not yet installed on the system.
3388 */
3389 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
3390 int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
3391 final ShortcutInfo info = new ShortcutInfo();
3392 info.user = UserHandleCompat.myUserHandle();
3393
3394 Bitmap icon = iconInfo.loadIcon(c, info, context);
3395 // the fallback icon
3396 if (icon == null) {
3397 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3398 } else {
3399 info.setIcon(icon);
3400 }
3401
3402 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3403 String title = (c != null) ? c.getString(titleIndex) : null;
3404 if (!TextUtils.isEmpty(title)) {
3405 info.title = Utilities.trim(title);
3406 }
3407 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3408 if (TextUtils.isEmpty(info.title)) {
3409 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3410 }
3411 } else {
3412 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3413 }
3414
3415 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3416 info.itemType = itemType;
3417 info.promisedIntent = intent;
3418 info.status = promiseType;
3419 return info;
3420 }
3421
3422 /**
3423 * Make an Intent object for a restored application or shortcut item that points
3424 * to the market page for the item.
3425 */
3426 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3427 ComponentName componentName = intent.getComponent();
3428 return getMarketIntent(componentName.getPackageName());
3429 }
3430
3431 static Intent getMarketIntent(String packageName) {
3432 return new Intent(Intent.ACTION_VIEW)
3433 .setData(new Uri.Builder()
3434 .scheme("market")
3435 .authority("details")
3436 .appendQueryParameter("id", packageName)
3437 .build());
3438 }
3439
3440 /**
3441 * Make an ShortcutInfo object for a shortcut that is an application.
3442 *
3443 * If c is not null, then it will be used to fill in missing data like the title and icon.
3444 */
3445 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3446 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3447 boolean allowMissingTarget, boolean useLowResIcon) {
3448 if (user == null) {
3449 Log.d(TAG, "Null user found in getShortcutInfo");
3450 return null;
3451 }
3452
3453 ComponentName componentName = intent.getComponent();
3454 if (componentName == null) {
3455 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3456 return null;
3457 }
3458
3459 Intent newIntent = new Intent(intent.getAction(), null);
3460 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3461 newIntent.setComponent(componentName);
3462 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3463 if ((lai == null) && !allowMissingTarget) {
3464 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3465 return null;
3466 }
3467
3468 final ShortcutInfo info = new ShortcutInfo();
3469 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3470 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3471 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3472 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3473 }
3474
3475 // from the db
3476 if (TextUtils.isEmpty(info.title) && c != null) {
3477 info.title = Utilities.trim(c.getString(titleIndex));
3478 }
3479
3480 // fall back to the class name of the activity
3481 if (info.title == null) {
3482 info.title = componentName.getClassName();
3483 }
3484
3485 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3486 info.user = user;
3487 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3488 if (lai != null) {
3489 info.flags = AppInfo.initFlags(lai);
3490 }
3491 return info;
3492 }
3493
3494 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3495 ItemInfoFilter f) {
3496 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3497 for (ItemInfo i : infos) {
3498 if (i instanceof ShortcutInfo) {
3499 ShortcutInfo info = (ShortcutInfo) i;
3500 ComponentName cn = info.getTargetComponent();
3501 if (cn != null && f.filterItem(null, info, cn)) {
3502 filtered.add(info);
3503 }
3504 } else if (i instanceof FolderInfo) {
3505 FolderInfo info = (FolderInfo) i;
3506 for (ShortcutInfo s : info.contents) {
3507 ComponentName cn = s.getTargetComponent();
3508 if (cn != null && f.filterItem(info, s, cn)) {
3509 filtered.add(s);
3510 }
3511 }
3512 } else if (i instanceof LauncherAppWidgetInfo) {
3513 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3514 ComponentName cn = info.providerName;
3515 if (cn != null && f.filterItem(null, info, cn)) {
3516 filtered.add(info);
3517 }
3518 }
3519 }
3520 return new ArrayList<ItemInfo>(filtered);
3521 }
3522
3523 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3524 final UserHandleCompat user) {
3525 ItemInfoFilter filter = new ItemInfoFilter() {
3526 @Override
3527 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3528 if (info.user == null) {
3529 return cn.equals(cname);
3530 } else {
3531 return cn.equals(cname) && info.user.equals(user);
3532 }
3533 }
3534 };
3535 return filterItemInfos(sBgItemsIdMap, filter);
3536 }
3537
3538 /**
3539 * Make an ShortcutInfo object for a shortcut that isn't an application.
3540 */
3541 @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
3542 int titleIndex, CursorIconInfo iconInfo) {
3543 final ShortcutInfo info = new ShortcutInfo();
3544 // Non-app shortcuts are only supported for current user.
3545 info.user = UserHandleCompat.myUserHandle();
3546 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3547
3548 // TODO: If there's an explicit component and we can't install that, delete it.
3549
3550 info.title = Utilities.trim(c.getString(titleIndex));
3551
3552 Bitmap icon = iconInfo.loadIcon(c, info, context);
3553 // the fallback icon
3554 if (icon == null) {
3555 icon = mIconCache.getDefaultIcon(info.user);
3556 info.usingFallbackIcon = true;
3557 }
3558 info.setIcon(icon);
3559 return info;
3560 }
3561
3562 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3563 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3564 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3565 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3566
3567 if (intent == null) {
3568 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3569 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3570 return null;
3571 }
3572
3573 Bitmap icon = null;
3574 boolean customIcon = false;
3575 ShortcutIconResource iconResource = null;
3576
3577 if (bitmap instanceof Bitmap) {
3578 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3579 customIcon = true;
3580 } else {
3581 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3582 if (extra instanceof ShortcutIconResource) {
3583 iconResource = (ShortcutIconResource) extra;
3584 icon = Utilities.createIconBitmap(iconResource.packageName,
3585 iconResource.resourceName, context);
3586 }
3587 }
3588
3589 final ShortcutInfo info = new ShortcutInfo();
3590
3591 // Only support intents for current user for now. Intents sent from other
3592 // users wouldn't get here without intent forwarding anyway.
3593 info.user = UserHandleCompat.myUserHandle();
3594 if (icon == null) {
3595 icon = mIconCache.getDefaultIcon(info.user);
3596 info.usingFallbackIcon = true;
3597 }
3598 info.setIcon(icon);
3599
3600 info.title = Utilities.trim(name);
3601 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3602 info.intent = intent;
3603 info.customIcon = customIcon;
3604 info.iconResource = iconResource;
3605
3606 return info;
3607 }
3608
3609 /**
3610 * Return an existing FolderInfo object if we have encountered this ID previously,
3611 * or make a new one.
3612 */
3613 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3614 // See if a placeholder was created for us already
3615 FolderInfo folderInfo = folders.get(id);
3616 if (folderInfo == null) {
3617 // No placeholder -- create a new instance
3618 folderInfo = new FolderInfo();
3619 folders.put(id, folderInfo);
3620 }
3621 return folderInfo;
3622 }
3623
3624
3625 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3626 return (provider != null) && (provider.provider != null)
3627 && (provider.provider.getPackageName() != null);
3628 }
3629
3630 public void dumpState() {
3631 Log.d(TAG, "mCallbacks=" + mCallbacks);
3632 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3633 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3634 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3635 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3636 if (mLoaderTask != null) {
3637 mLoaderTask.dumpState();
3638 } else {
3639 Log.d(TAG, "mLoaderTask=null");
3640 }
3641 }
3642
3643 public Callbacks getCallback() {
3644 return mCallbacks != null ? mCallbacks.get() : null;
3645 }
3646
3647 /**
3648 * @return {@link FolderInfo} if its already loaded.
3649 */
3650 public FolderInfo findFolderById(Long folderId) {
3651 synchronized (sBgLock) {
3652 return sBgFolders.get(folderId);
3653 }
3654 }
3655
3656 /**
3657 * @return the looper for the worker thread which can be used to start background tasks.
3658 */
3659 public static Looper getWorkerLooper() {
3660 return sWorkerThread.getLooper();
3661 }
3662 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.app.SearchManager;
19 import android.appwidget.AppWidgetProviderInfo;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.ContentProviderOperation;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent.ShortcutIconResource;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ProviderInfo;
31 import android.content.pm.ResolveInfo;
32 import android.database.Cursor;
33 import android.graphics.Bitmap;
34 import android.net.Uri;
35 import android.os.Build;
36 import android.os.Environment;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Looper;
40 import android.os.Parcelable;
41 import android.os.Process;
42 import android.os.SystemClock;
43 import android.os.TransactionTooLargeException;
44 import android.provider.BaseColumns;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.util.LongSparseArray;
48 import android.util.Pair;
49 import com.android.launcher3.compat.AppWidgetManagerCompat;
50 import com.android.launcher3.compat.LauncherActivityInfoCompat;
51 import com.android.launcher3.compat.LauncherAppsCompat;
52 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
53 import com.android.launcher3.compat.PackageInstallerCompat;
54 import com.android.launcher3.compat.UserHandleCompat;
55 import com.android.launcher3.compat.UserManagerCompat;
56 import com.android.launcher3.model.WidgetsModel;
57 import com.android.launcher3.util.ComponentKey;
58 import com.android.launcher3.util.CursorIconInfo;
59 import com.android.launcher3.util.LongArrayMap;
60 import com.android.launcher3.util.ManagedProfileHeuristic;
61 import com.android.launcher3.util.Thunk;
62 import java.lang.ref.WeakReference;
63 import java.net.URISyntaxException;
64 import java.security.InvalidParameterException;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Collection;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.Map.Entry;
75 import java.util.Set;
76
77
78 /**
79 * Maintains in-memory state of the Launcher. It is expected that there should be only one
80 * LauncherModel object held in a static. Also provide APIs for updating the database state
81 * for the Launcher.
82 */
83 public class LauncherModel extends BroadcastReceiver implements LauncherAppsCompat.OnAppsChangedCallbackC🔵
84 static final boolean DEBUG_LOADERS = false;
85
86 private static final boolean DEBUG_RECEIVER = false;
87
88 private static final boolean REMOVE_UNRESTORED_ICONS = true;
89
90 static final String TAG = "Launcher.Model";
91
92 public static final int LOADER_FLAG_NONE = 0;
93
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95
96 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
97
98 // batch size for the workspace icons
99 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
100
101 private static final long INVALID_SCREEN_ID = -1L;
102
103 @Thunk
104 final boolean mAppsCanBeOnRemoveableStorage;
105
106 private final boolean mOldContentProviderExists;
107
108 @Thunk
109 final LauncherAppState mApp;
110
111 @Thunk
112 final Object mLock = new Object();
113
114 @Thunk
115 DeferredHandler mHandler = new DeferredHandler();
116
117 @Thunk
118 LoaderTask mLoaderTask;
119
120 @Thunk
121 boolean mIsLoaderTaskRunning;
122
123 @Thunk
124 boolean mHasLoaderCompletedOnce;
125
126 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
127
128 @Thunk
129 static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
130
131 static {
132 sWorkerThread.start();
133 }
134
135 @Thunk
136 static final Handler sWorker = new Handler(sWorkerThread.getLooper());
137
138 // We start off with everything not loaded. After that, we assume that
139 // our monitoring of the package manager provides all updates and we never
140 // need to do a requery. These are only ever touched from the loader thread.
141 @Thunk
142 boolean mWorkspaceLoaded;
143
144 @Thunk
145 boolean mAllAppsLoaded;
146
147 // When we are loading pages synchronously, we can't just post the binding of items on the side
148 // pages as this delays the rotation process. Instead, we wait for a callback from the first
149 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
150 // a normal load, we also clear this set of Runnables.
151 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
152
153 /**
154 * Set of runnables to be called on the background thread after the workspace binding
155 * is complete.
156 */
157 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
158
159 @Thunk
160 WeakReference<Callbacks> mCallbacks;
161
162 // < only access in worker thread >
163 // < only access in worker thread >
164 AllAppsList mBgAllAppsList;
165
166 // Entire list of widgets.
167 // Entire list of widgets.
168 WidgetsModel mBgWidgetsModel;
169
170 // The lock that must be acquired before referencing any static bg data structures. Unlike
171 // other locks, this one can generally be held long-term because we never expect any of these
172 // static data structures to be referenced outside of the worker thread except on the first
173 // load after configuration change.
174 static final Object sBgLock = new Object();
175
176 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
177 // LauncherModel to their ids
178 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
179
180 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
181 // created by LauncherModel that are directly on the home screen (however, no widgets or
182 // shortcuts within folders).
183 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
184
185 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
186 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
187 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
188 new ArrayList<LauncherAppWidgetInfo>();
189
190 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
191 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
192
193 // sBgWorkspaceScreens is the ordered set of workspace screens.
194 // sBgWorkspaceScreens is the ordered set of workspace screens.
195 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
196
197 // sBgWidgetProviders is the set of widget providers including custom internal widgets
198 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
199
200 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
201 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages = new HashMap<UserHandleComp🔵
202
203 // </ only access in worker thread >
204 @Thunk
205 IconCache mIconCache;
206
207 @Thunk
208 final LauncherAppsCompat mLauncherApps;
209
210 @Thunk
211 final UserManagerCompat mUserManager;
212
213 public interface Callbacks {
214 public boolean setLoadOnResume();
215
216 public int getCurrentWorkspaceScreen();
217
218 public void startBinding();
219
220 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
221 boolean forceAnimateIcons);
222
223 public void bindScreens(ArrayList<Long> orderedScreenIds);
224
225 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
226
227 public abstract void bindFolders(LongArrayMap<FolderInfo> folders);
228
229 public abstract void finishBindingItems();
230
231 public void bindAppWidget(LauncherAppWidgetInfo info);
232
233 public void bindAllApplications(ArrayList<AppInfo> apps);
234
235 public void bindAppsAdded(ArrayList<Long> newScreens,
236 ArrayList<ItemInfo> addNotAnimated,
237 ArrayList<ItemInfo> addAnimated,
238 ArrayList<AppInfo> addedApps);
239
240 public void bindAppsUpdated(ArrayList<AppInfo> apps);
241
242 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
243 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
244
245 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
246
247 public abstract void bindRestoreItemsChange(HashSet<ItemInfo> updates);
248
249 public void bindComponentsRemoved(ArrayList<String> packageNames,
250 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
251
252 public abstract void bindAllPackages(WidgetsModel model);
253
254 public void bindSearchablesChanged();
255
256 public boolean isAllAppsButtonRank(int rank);
257
258 public void onPageBoundSynchronously(int page);
259
260 public void dumpLogsToLocalData();
261 }
262
263 public interface ItemInfoFilter {
264 public abstract boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
265 }
266
267 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
268 Context context = app.getContext();
269 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
270 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
271 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
272 // resource string.
273 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
274 ProviderInfo providerInfo = context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY,🔵
275 ProviderInfo redirectProvider = context.getPackageManager().resolveContentProvider(redirectAuthor🔵
276 Log.d(TAG, "Old launcher provider: " + oldProvider);
277 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
278 if (mOldContentProviderExists) {
279 Log.d(TAG, "Old launcher provider exists.");
280 } else {
281 Log.d(TAG, "Old launcher provider does not exist.");
282 }
283 mApp = app;
284 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
285 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
286 mIconCache = iconCache;
287 mLauncherApps = LauncherAppsCompat.getInstance(context);
288 mUserManager = UserManagerCompat.getInstance(context);
289 }
290
291 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
292 * posted on the main thread handler. */
293 @Thunk
294 void runOnMainThread(Runnable r) {
295 if (sWorkerThread.getThreadId() == Process.myTid()) {
296 // If we are on the worker thread, post onto the main handler
297 mHandler.post(r);
298 } else {
299 r.run();
300 }
301 }
302
303 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
304 * posted on the worker thread handler. */
305 private static void runOnWorkerThread(Runnable r) {
306 if (sWorkerThread.getThreadId() == Process.myTid()) {
307 r.run();
308 } else {
309 // If we are not on the worker thread, then post to the worker handler
310 sWorker.post(r);
311 }
312 }
313
314 /**
315 * Runs the specified runnable after the loader is complete
316 */
317 @Thunk
318 void runAfterBindCompletes(Runnable r) {
319 if (isLoadingWorkspace() || (!mHasLoaderCompletedOnce)) {
320 synchronized(mBindCompleteRunnables) {
321 mBindCompleteRunnables.add(r);
322 }
323 } else {
324 runOnWorkerThread(r);
325 }
326 }
327
328 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
329 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
330 }
331
332 public void setPackageState(final PackageInstallInfo installInfo) {
333 Runnable updateRunnable = new Runnable() {
334 @Override
335 public void run() {
336 synchronized(sBgLock) {
337 final HashSet<ItemInfo> updates = new HashSet<>();
338 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
339 // Ignore install success events as they are handled by Package add events.
340 return;
341 }
342 for (ItemInfo info : sBgItemsIdMap) {
343 if (info instanceof ShortcutInfo) {
344 ShortcutInfo si = ((ShortcutInfo) (info));
345 ComponentName cn = si.getTargetComponent();
346 if ((si.isPromise() && (cn != null)) && installInfo.packageName.equals(cn.get🔵
347 si.setInstallProgress(installInfo.progress);
348 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
349 // Mark this info as broken.
350 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
351 }
352 updates.add(si);
353 }
354 }
355 }
356 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
357 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
358 widget.installProgress = installInfo.progress;
359 updates.add(widget);
360 }
361 }
362 if (!updates.isEmpty()) {
363 // Push changes to the callback.
364 Runnable r = new Runnable() {
365 public void run() {
366 Callbacks callbacks = getCallback();
367 if (callbacks != null) {
368 callbacks.bindRestoreItemsChange(updates);
369 }
370 }
371 };
372 mHandler.post(r);
373 }
374 }
375 }
376 };
377 runOnWorkerThread(updateRunnable);
378 }
379
380 /**
381 * Updates the icons and label of all pending icons for the provided package name.
382 */
383 public void updateSessionDisplayInfo(final String packageName) {
384 Runnable updateRunnable = new Runnable() {
385 @Override
386 public void run() {
387 synchronized(sBgLock) {
388 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
389 final UserHandleCompat user = UserHandleCompat.myUserHandle();
390 for (ItemInfo info : sBgItemsIdMap) {
391 if (info instanceof ShortcutInfo) {
392 ShortcutInfo si = ((ShortcutInfo) (info));
393 ComponentName cn = si.getTargetComponent();
394 if ((si.isPromise() && (cn != null)) && packageName.equals(cn.getPackageName(🔵
395 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
396 // For auto install apps update the icon as well as label.
397 mIconCache.getTitleAndIcon(si, si.promisedIntent, user, si.shouldUseL🔵
398 } else {
399 // Only update the icon for restored apps.
400 si.updateIcon(mIconCache);
401 }
402 updates.add(si);
403 }
404 }
405 }
406 if (!updates.isEmpty()) {
407 // Push changes to the callback.
408 Runnable r = new Runnable() {
409 public void run() {
410 Callbacks callbacks = getCallback();
411 if (callbacks != null) {
412 callbacks.bindShortcutsChanged(updates, new ArrayList<ShortcutInfo>()🔵
413 }
414 }
415 };
416 mHandler.post(r);
417 }
418 }
419 }
420 };
421 runOnWorkerThread(updateRunnable);
422 }
423
424 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
425 final Callbacks callbacks = getCallback();
426 if (allAppsApps == null) {
427 throw new RuntimeException("allAppsApps must not be null");
428 }
429 if (allAppsApps.isEmpty()) {
430 return;
431 }
432 // Process the newly added applications and add them to the database first
433 Runnable r = new Runnable() {
434 public void run() {
435 runOnMainThread(new Runnable() {
436 public void run() {
437 Callbacks cb = getCallback();
438 if ((callbacks == cb) && (cb != null)) {
439 callbacks.bindAppsAdded(null, null, null, allAppsApps);
440 }
441 }
442 });
443 }
444 };
445 runOnWorkerThread(r);
446 }
447
448 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos, int[] xy, 🔵
449 LauncherAppState app = LauncherAppState.getInstance();
450 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
451 final int xCount = ((int) (profile.numColumns));
452 final int yCount = ((int) (profile.numRows));
453 boolean[][] occupied = new boolean[xCount][yCount];
454 if (occupiedPos != null) {
455 for (ItemInfo r : occupiedPos) {
456 int right = r.cellX + r.spanX;
457 int bottom = r.cellY + r.spanY;
458 for (int x = r.cellX; ((0 <= x) && (x < right)) && (x < xCount); x++) {
459 for (int y = r.cellY; ((0 <= y) && (y < bottom)) && (y < yCount); y++) {
460 occupied[x][y] = true;
461 }
462 }
463 }
464 }
465 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
466 }
467
468 /**
469 * Find a position on the screen for the given size or adds a new screen.
470 * @return screenId and the coordinates for the item.
471 */
472 @Thunk
473 Pair<Long, int[]> findSpaceForItem(Context context, ArrayList<Long> workspaceScreens, ArrayList<Long>🔵
474 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
475 // Use sBgItemsIdMap as all the items are already loaded.
476 assertWorkspaceLoaded();
477 synchronized(sBgLock) {
478 for (ItemInfo info : sBgItemsIdMap) {
479 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
480 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
481 if (items == null) {
482 items = new ArrayList<>();
483 screenItems.put(info.screenId, items);
484 }
485 items.add(info);
486 }
487 }
488 }
489 // Find appropriate space for the item.
490 long screenId = 0;
491 int[] cordinates = new int[2];
492 boolean found = false;
493 int screenCount = workspaceScreens.size();
494 // First check the preferred screen.
495 int preferredScreenIndex = (workspaceScreens.isEmpty()) ? 0 : 1;
496 if (preferredScreenIndex < screenCount) {
497 screenId = workspaceScreens.get(preferredScreenIndex);
498 found = findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, span🔵
499 }
500 if (!found) {
501 // Search on any of the screens starting from the first screen.
502 for (int screen = 1; screen < screenCount; screen++) {
503 screenId = workspaceScreens.get(screen);
504 if (findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, span🔵
505 // We found a space for it
506 found = true;
507 break;
508 }
509 }
510 }
511 if (!found) {
512 // Still no position found. Add a new screen to the end.
513 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
514 // Save the screen id for binding in the workspace
515 workspaceScreens.add(screenId);
516 addedWorkspaceScreensFinal.add(screenId);
517 // If we still can't find an empty space, then God help us all!!!
518 if (!findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, spanY))🔵
519 throw new RuntimeException("Can't find space to add the item");
520 }
521 }
522 return Pair.create(screenId, cordinates);
523 }
524
525 /**
526 * Adds the provided items to the workspace.
527 */
528 public void addAndBindAddedWorkspaceItems(final Context context, final ArrayList<? extends ItemInfo> 🔵
529 final Callbacks callbacks = getCallback();
530 if (workspaceApps.isEmpty()) {
531 return;
532 }
533 // Process the newly added applications and add them to the database first
534 Runnable r = new Runnable() {
535 public void run() {
536 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
537 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
538 // Get the list of workspace screens. We need to append to this list and
539 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
540 // called.
541 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
542 synchronized(sBgLock) {
543 for (ItemInfo item : workspaceApps) {
544 if (item instanceof ShortcutInfo) {
545 // Short-circuit this logic if the icon exists somewhere on the workspace
546 if (shortcutExists(context, item.getIntent(), item.user)) {
547 continue;
548 }
549 }
550 // Find appropriate space for the item.
551 Pair<Long, int[]> coords = findSpaceForItem(context, workspaceScreens, addedWorks🔵
552 long screenId = coords.first;
553 int[] cordinates = coords.second;
554 ItemInfo itemInfo;
555 if ((item instanceof ShortcutInfo) || (item instanceof FolderInfo)) {
556 itemInfo = item;
557 } else if (item instanceof AppInfo) {
558 itemInfo = ((AppInfo) (item)).makeShortcut();
559 } else {
560 throw new RuntimeException("Unexpected info type");
561 }
562 // Add the shortcut to the db
563 addItemToDatabase(context, itemInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP🔵
564 // Save the ShortcutInfo for binding in the workspace
565 addedShortcutsFinal.add(itemInfo);
566 }
567 }
568 // Update the workspace screens
569 updateWorkspaceScreenOrder(context, workspaceScreens);
570 if (!addedShortcutsFinal.isEmpty()) {
571 runOnMainThread(new Runnable() {
572 public void run() {
573 Callbacks cb = getCallback();
574 if ((callbacks == cb) && (cb != null)) {
575 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
576 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
577 if (!addedShortcutsFinal.isEmpty()) {
578 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
579 long lastScreenId = info.screenId;
580 for (ItemInfo i : addedShortcutsFinal) {
581 if (i.screenId == lastScreenId) {
582 addAnimated.add(i);
583 } else {
584 addNotAnimated.add(i);
585 }
586 }
587 }
588 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, addNotAnimated, addAn🔵
589 }
590 }
591 });
592 }
593 }
594 };
595 runOnWorkerThread(r);
596 }
597
598 private void unbindItemInfosAndClearQueuedBindRunnables() {
599 if (sWorkerThread.getThreadId() == Process.myTid()) {
600 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " + "mai🔵
601 }
602 // Clear any deferred bind runnables
603 synchronized(mDeferredBindRunnables) {
604 mDeferredBindRunnables.clear();
605 }
606 // Remove any queued UI runnables
607 mHandler.cancelAll();
608 // Unbind all the workspace items
609 unbindWorkspaceItemsOnMainThread();
610 }
611
612 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
613 void unbindWorkspaceItemsOnMainThread() {
614 // Ensure that we don't use the same workspace items data structure on the main thread
615 // by making a copy of workspace items first.
616 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
617 synchronized(sBgLock) {
618 tmpItems.addAll(sBgWorkspaceItems);
619 tmpItems.addAll(sBgAppWidgets);
620 }
621 Runnable r = new Runnable() {
622 @Override
623 public void run() {
624 for (ItemInfo item : tmpItems) {
625 item.unbind();
626 }
627 }
628 };
629 runOnMainThread(r);
630 }
631
632 /**
633 * Adds an item to the DB if it was not created previously, or move it to a new
634 * <container, screen, cellX, cellY>
635 */
636 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, in🔵
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(final long itemId, final ItemInfo item, StackTraceElement[] stackTrac🔵
647 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
648 if ((modelItem != null) && (item != modelItem)) {
649 // check all the data is consistent
650 if ((modelItem instanceof ShortcutInfo) && (item instanceof ShortcutInfo)) {
651 ShortcutInfo modelShortcut = ((ShortcutInfo) (modelItem));
652 ShortcutInfo shortcut = ((ShortcutInfo) (item));
653 if ((((((((((modelShortcut.title.toString().equals(shortcut.title.toString()) && modelSho🔵
654 // For all intents and purposes, this is the same object
655 return;
656 }
657 }
658 // the modelItem needs to match up perfectly with item if our model is
659 // to be consistent with the database-- for now, just require
660 // modelItem == item or the equality check above
661 String msg = ((("item: " + (item != null ? item.toString() : "null")) + "modelItem: ") + (mod🔵
662 RuntimeException e = new RuntimeException(msg);
663 if (stackTrace != null) {
664 e.setStackTrace(stackTrace);
665 }
666 throw e;
667 }
668 }
669
670 static void checkItemInfo(final ItemInfo item) {
671 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
672 final long itemId = item.id;
673 Runnable r = new Runnable() {
674 public void run() {
675 synchronized (sBgLock) {
676 checkItemInfoLocked(itemId, item, stackTrace);
677 }
678 }
679 };
680 runOnWorkerThread(r);
681 }
682
683 static void updateItemInDatabaseHelper(Context context, final ContentValues values, final ItemInfo it🔵
684 final long itemId = item.id;
685 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
686 final ContentResolver cr = context.getContentResolver();
687 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
688 Runnable r = new Runnable() {
689 public void run() {
690 cr.update(uri, values, null, null);
691 updateItemArrays(item, itemId, stackTrace);
692 }
693 };
694 runOnWorkerThread(r);
695 }
696
697 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, f🔵
698 final ContentResolver cr = context.getContentResolver();
699 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
700 Runnable r = new Runnable() {
701 public void run() {
702 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
703 int count = items.size();
704 for (int i = 0; i < count; i++) {
705 ItemInfo item = items.get(i);
706 final long itemId = item.id;
707 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
708 ContentValues values = valuesList.get(i);
709 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
710 updateItemArrays(item, itemId, stackTrace);
711 }
712 try {
713 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
714 } catch (java.lang.Exception e) {
715 e.printStackTrace();
716 }
717 }
718 };
719 runOnWorkerThread(r);
720 }
721
722 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
723 // Lock on mBgLock *after* the db operation
724 synchronized(sBgLock) {
725 checkItemInfoLocked(itemId, item, stackTrace);
726 if ((item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) && (item.container != La🔵
727 // Item is in a folder, make sure this folder exists
728 if (!sBgFolders.containsKey(item.container)) {
729 // An items container is being set to a that of an item which is not in
730 // the list of Folders.
731 String msg = ((("item: " + item) + " container being set to: ") + item.container) + "🔵
732 Log.e(TAG, msg);
733 }
734 }
735 // Items are added/removed from the corresponding FolderInfo elsewhere, such
736 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
737 // that are on the desktop, as appropriate
738 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
739 if ((modelItem != null) && ((modelItem.container == LauncherSettings.Favorites.CONTAINER_DESK🔵
740 switch (modelItem.itemType) {
741 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
742 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
743 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
744 if (!sBgWorkspaceItems.contains(modelItem)) {
745 sBgWorkspaceItems.add(modelItem);
746 }
747 break;
748 default :
749 break;
750 }
751 } else {
752 sBgWorkspaceItems.remove(modelItem);
753 }
754 }
755 }
756
757 /**
758 * Move an item in the DB to a new <container, screen, cellX, cellY>
759 */
760 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container, fin🔵
761 item.container = container;
762 item.cellX = cellX;
763 item.cellY = cellY;
764 // We store hotseat items in canonical form which is this orientation invariant position
765 // in the hotseat
766 if (((context instanceof Launcher) && (screenId < 0)) && (container == LauncherSettings.Favorites🔵
767 item.screenId = ((Launcher) (context)).getHotseat().getOrderInHotseat(cellX, cellY);
768 } else {
769 item.screenId = screenId;
770 }
771 final ContentValues values = new ContentValues();
772 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
773 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
774 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
775 values.put(LauncherSettings.Favorites.RANK, item.rank);
776 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
777 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
778 }
779
780 /**
781 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
782 * cellX, cellY have already been updated on the ItemInfos.
783 */
784 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
785 final long container, final int screen) {
786
787 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
788 int count = items.size();
789
790 for (int i = 0; i < count; i++) {
791 ItemInfo item = items.get(i);
792 item.container = container;
793
794 // We store hotseat items in canonical form which is this orientation invariant position
795 // in the hotseat
796 if (context instanceof Launcher && screen < 0 &&
797 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
798 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
799 item.cellY);
800 } else {
801 item.screenId = screen;
802 }
803
804 final ContentValues values = new ContentValues();
805 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
806 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
807 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
808 values.put(LauncherSettings.Favorites.RANK, item.rank);
809 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
810
811 contentValues.add(values);
812 }
813 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
814 }
815
816 /**
817 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
818 */
819 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
820 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
821 item.container = container;
822 item.cellX = cellX;
823 item.cellY = cellY;
824 item.spanX = spanX;
825 item.spanY = spanY;
826
827 // We store hotseat items in canonical form which is this orientation invariant position
828 // in the hotseat
829 if (context instanceof Launcher && screenId < 0 &&
830 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
831 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
832 } else {
833 item.screenId = screenId;
834 }
835
836 final ContentValues values = new ContentValues();
837 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
838 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
839 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
840 values.put(LauncherSettings.Favorites.RANK, item.rank);
841 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
842 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
843 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
844
845 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
846 }
847
848 /**
849 * Update an item to the database in a specified container.
850 */
851 public static void updateItemInDatabase(Context context, final ItemInfo item) {
852 final ContentValues values = new ContentValues();
853 item.onAddToDatabase(context, values);
854 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
855 }
856
857 private void assertWorkspaceLoaded() {
858 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
859 throw new RuntimeException("Trying to add shortcut while loader is running");
860 }
861 }
862
863 /**
864 * Returns true if the shortcuts already exists on the workspace. This must be called after
865 * the workspace has been loaded. We identify a shortcut by its intent.
866 */
867 @Thunk
868 boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
869 assertWorkspaceLoaded();
870 final String intentWithPkg;
871 final String intentWithoutPkg;
872 if (intent.getComponent() != null) {
873 // If component is not null, an intent with null package will produce
874 // the same result and should also be a match.
875 String packageName = intent.getComponent().getPackageName();
876 if (intent.getPackage() != null) {
877 intentWithPkg = intent.toUri(0);
878 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
879 } else {
880 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
881 intentWithoutPkg = intent.toUri(0);
882 }
883 } else {
884 intentWithPkg = intent.toUri(0);
885 intentWithoutPkg = intent.toUri(0);
886 }
887 synchronized(sBgLock) {
888 for (ItemInfo item : sBgItemsIdMap) {
889 if (item instanceof ShortcutInfo) {
890 ShortcutInfo info = ((ShortcutInfo) (item));
891 Intent targetIntent = (info.promisedIntent == null) ? info.intent : info.promisedInte🔵
892 if ((targetIntent != null) && info.user.equals(user)) {
893 String s = targetIntent.toUri(0);
894 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
895 return true;
896 }
897 }
898 }
899 }
900 }
901 return false;
902 }
903
904 /**
905 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
906 */
907 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
908 final ContentResolver cr = context.getContentResolver();
909 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, "_id=? and (itemType=? or itemT🔵
910 try {
911 if (c.moveToFirst()) {
912 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
913 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
914 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
915 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
916 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
917 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
918 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
919 FolderInfo folderInfo = null;
920 switch (c.getInt(itemTypeIndex)) {
921 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
922 folderInfo = findOrMakeFolder(folderList, id);
923 break;
924 }
925 // Do not trim the folder label, as is was set by the user.
926 folderInfo.title = c.getString(titleIndex);
927 folderInfo.id = id;
928 folderInfo.container = c.getInt(containerIndex);
929 folderInfo.screenId = c.getInt(screenIndex);
930 folderInfo.cellX = c.getInt(cellXIndex);
931 folderInfo.cellY = c.getInt(cellYIndex);
932 folderInfo.options = c.getInt(optionsIndex);
933 return folderInfo;
934 }
935 } finally {
936 c.close();
937 }
938 return null;
939 }
940
941 /**
942 * Add an item to the database in a specified container. Sets the container, screen, cellX and
943 * cellY fields of the item. Also assigns an ID to the item.
944 */
945 public static void addItemToDatabase(Context context, final ItemInfo item, final long container, fina🔵
946 item.container = container;
947 item.cellX = cellX;
948 item.cellY = cellY;
949 // We store hotseat items in canonical form which is this orientation invariant position
950 // in the hotseat
951 if (((context instanceof Launcher) && (screenId < 0)) && (container == LauncherSettings.Favorites🔵
952 item.screenId = ((Launcher) (context)).getHotseat().getOrderInHotseat(cellX, cellY);
953 } else {
954 item.screenId = screenId;
955 }
956 final ContentValues values = new ContentValues();
957 final ContentResolver cr = context.getContentResolver();
958 item.onAddToDatabase(context, values);
959 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
960 values.put(LauncherSettings.Favorites._ID, item.id);
961 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
962 Runnable r = new Runnable() {
963 public void run() {
964 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
965 // Lock on mBgLock *after* the db operation
966 synchronized(sBgLock) {
967 checkItemInfoLocked(item.id, item, stackTrace);
968 sBgItemsIdMap.put(item.id, item);
969 switch (item.itemType) {
970 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
971 sBgFolders.put(item.id, ((FolderInfo) (item)));
972 // Fall through
973 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
974 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
975 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) || (item🔵
976 sBgWorkspaceItems.add(item);
977 } else if (!sBgFolders.containsKey(item.container)) {
978 // Adding an item to a folder that doesn't exist.
979 String msg = (("adding item: " + item) + " to a folder that ") + " doesn'🔵
980 Log.e(TAG, msg);
981 }
982 break;
983 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
984 sBgAppWidgets.add(((LauncherAppWidgetInfo) (item)));
985 break;
986 }
987 }
988 }
989 };
990 runOnWorkerThread(r);
991 }
992
993 /**
994 * Creates a new unique child id, for a given cell span across all layouts.
995 */
996 static int getCellLayoutChildId(
997 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
998 return (((int) container & 0xFF) << 24)
999 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1000 }
1001
1002 private static ArrayList<ItemInfo> getItemsByPackageName(final String pn, final UserHandleCompat user🔵
1003 ItemInfoFilter filter = new ItemInfoFilter() {
1004 @Override
1005 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1006 return cn.getPackageName().equals(pn) && info.user.equals(user);
1007 }
1008 };
1009 return filterItemInfos(sBgItemsIdMap, filter);
1010 }
1011
1012 /**
1013 * Removes all the items from the database corresponding to the specified package.
1014 */
1015 static void deletePackageFromDatabase(Context context, final String pn,
1016 final UserHandleCompat user) {
1017 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1018 }
1019
1020 /**
1021 * Removes the specified item from the database
1022 * @param context
1023 * @param item
1024 */
1025 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1026 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1027 items.add(item);
1028 deleteItemsFromDatabase(context, items);
1029 }
1030
1031 /**
1032 * Removes the specified items from the database
1033 * @param context
1034 * @param item
1035 */
1036 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1037 final ContentResolver cr = context.getContentResolver();
1038 Runnable r = new Runnable() {
1039 public void run() {
1040 for (ItemInfo item : items) {
1041 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1042 cr.delete(uri, null, null);
1043 // Lock on mBgLock *after* the db operation
1044 synchronized(sBgLock) {
1045 switch (item.itemType) {
1046 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1047 sBgFolders.remove(item.id);
1048 for (ItemInfo info : sBgItemsIdMap) {
1049 if (info.container == item.id) {
1050 // We are deleting a folder which still contains items that
1051 // think they are contained by that folder.
1052 String msg = (((("deleting a folder (" + item) + ") which still "🔵
1053 Log.e(TAG, msg);
1054 }
1055 }
1056 sBgWorkspaceItems.remove(item);
1057 break;
1058 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1059 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1060 sBgWorkspaceItems.remove(item);
1061 break;
1062 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1063 sBgAppWidgets.remove(((LauncherAppWidgetInfo) (item)));
1064 break;
1065 }
1066 sBgItemsIdMap.remove(item.id);
1067 }
1068 }
1069 }
1070 };
1071 runOnWorkerThread(r);
1072 }
1073
1074 /**
1075 * Update the order of the workspace screens in the database. The array list contains
1076 * a list of screen ids in the order that they should appear.
1077 */
1078 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1079 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1080 final ContentResolver cr = context.getContentResolver();
1081 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1082 // Remove any negative screen ids -- these aren't persisted
1083 Iterator<Long> iter = screensCopy.iterator();
1084 while (iter.hasNext()) {
1085 long id = iter.next();
1086 if (id < 0) {
1087 iter.remove();
1088 }
1089 }
1090 Runnable r = new Runnable() {
1091 @Override
1092 public void run() {
1093 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1094 // Clear the table
1095 ops.add(ContentProviderOperation.newDelete(uri).build());
1096 int count = screensCopy.size();
1097 for (int i = 0; i < count; i++) {
1098 ContentValues v = new ContentValues();
1099 long screenId = screensCopy.get(i);
1100 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1101 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1102 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1103 }
1104 try {
1105 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1106 } catch (java.lang.Exception ex) {
1107 throw new RuntimeException(ex);
1108 }
1109 synchronized(sBgLock) {
1110 sBgWorkspaceScreens.clear();
1111 sBgWorkspaceScreens.addAll(screensCopy);
1112 }
1113 }
1114 };
1115 runOnWorkerThread(r);
1116 }
1117
1118 /**
1119 * Remove the contents of the specified folder from the database
1120 */
1121 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1122 final ContentResolver cr = context.getContentResolver();
1123 Runnable r = new Runnable() {
1124 public void run() {
1125 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1126 // Lock on mBgLock *after* the db operation
1127 synchronized(sBgLock) {
1128 sBgItemsIdMap.remove(info.id);
1129 sBgFolders.remove(info.id);
1130 sBgWorkspaceItems.remove(info);
1131 }
1132 cr.delete(LauncherSettings.Favorites.CONTENT_URI, (LauncherSettings.Favorites.CONTAINER +🔵
1133 // Lock on mBgLock *after* the db operation
1134 synchronized(sBgLock) {
1135 for (ItemInfo childInfo : info.contents) {
1136 sBgItemsIdMap.remove(childInfo.id);
1137 }
1138 }
1139 }
1140 };
1141 runOnWorkerThread(r);
1142 }
1143
1144 /**
1145 * Set this as the current Launcher activity object for the loader.
1146 */
1147 public void initialize(Callbacks callbacks) {
1148 synchronized(mLock) {
1149 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1150 // workspace to prevent leaking Launcher activities on orientation change.
1151 unbindItemInfosAndClearQueuedBindRunnables();
1152 mCallbacks = new WeakReference<Callbacks>(callbacks);
1153 }
1154 }
1155
1156 @Override
1157 public void onPackageChanged(String packageName, UserHandleCompat user) {
1158 int op = PackageUpdatedTask.OP_UPDATE;
1159 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1160 user));
1161 }
1162
1163 @Override
1164 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1165 int op = PackageUpdatedTask.OP_REMOVE;
1166 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1167 user));
1168 }
1169
1170 @Override
1171 public void onPackageAdded(String packageName, UserHandleCompat user) {
1172 int op = PackageUpdatedTask.OP_ADD;
1173 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1174 user));
1175 }
1176
1177 @Override
1178 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1179 boolean replacing) {
1180 if (!replacing) {
1181 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1182 user));
1183 if (mAppsCanBeOnRemoveableStorage) {
1184 // Only rebind if we support removable storage. It catches the
1185 // case where
1186 // apps on the external sd card need to be reloaded
1187 startLoaderFromBackground();
1188 }
1189 } else {
1190 // If we are replacing then just update the packages in the list
1191 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1192 packageNames, user));
1193 }
1194 }
1195
1196 @Override
1197 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1198 boolean replacing) {
1199 if (!replacing) {
1200 enqueuePackageUpdated(new PackageUpdatedTask(
1201 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1202 user));
1203 }
1204 }
1205
1206 /**
1207 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1208 * ACTION_PACKAGE_CHANGED.
1209 */
1210 @Override
1211 public void onReceive(Context context, Intent intent) {
1212 if (DEBUG_RECEIVER) {
1213 Log.d(TAG, "onReceive intent=" + intent);
1214 }
1215 final String action = intent.getAction();
1216 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1217 // If we have changed locale we need to clear out the labels in all apps/workspace.
1218 forceReload();
1219 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || SearchManager.IN🔵
1220 Callbacks callbacks = getCallback();
1221 if (callbacks != null) {
1222 callbacks.bindSearchablesChanged();
1223 }
1224 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) || LauncherAppsCompat.A🔵
1225 forceReload();
1226 }
1227 }
1228
1229 void forceReload() {
1230 resetLoadedState(true, true);
1231
1232 // Do this here because if the launcher activity is running it will be restarted.
1233 // If it's not running startLoaderFromBackground will merely tell it that it needs
1234 // to reload.
1235 startLoaderFromBackground();
1236 }
1237
1238 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1239 synchronized(mLock) {
1240 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1241 // mWorkspaceLoaded to true later
1242 stopLoaderLocked();
1243 if (resetAllAppsLoaded) {
1244 mAllAppsLoaded = false;
1245 }
1246 if (resetWorkspaceLoaded) {
1247 mWorkspaceLoaded = false;
1248 }
1249 }
1250 }
1251
1252 /**
1253 * When the launcher is in the background, it's possible for it to miss paired
1254 * configuration changes. So whenever we trigger the loader from the background
1255 * tell the launcher that it needs to re-run the loader when it comes back instead
1256 * of doing it now.
1257 */
1258 public void startLoaderFromBackground() {
1259 boolean runLoader = false;
1260 Callbacks callbacks = getCallback();
1261 if (callbacks != null) {
1262 // Only actually run the loader if they're not paused.
1263 if (!callbacks.setLoadOnResume()) {
1264 runLoader = true;
1265 }
1266 }
1267 if (runLoader) {
1268 startLoader(PagedView.INVALID_RESTORE_PAGE);
1269 }
1270 }
1271
1272 /**
1273 * If there is already a loader task running, tell it to stop.
1274 */
1275 private void stopLoaderLocked() {
1276 LoaderTask oldTask = mLoaderTask;
1277 if (oldTask != null) {
1278 oldTask.stopLocked();
1279 }
1280 }
1281
1282 public boolean isCurrentCallbacks(Callbacks callbacks) {
1283 return (mCallbacks != null && mCallbacks.get() == callbacks);
1284 }
1285
1286 public void startLoader(int synchronousBindPage) {
1287 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1288 }
1289
1290 public void startLoader(int synchronousBindPage, int loadFlags) {
1291 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1292 InstallShortcutReceiver.enableInstallQueue();
1293 synchronized(mLock) {
1294 // Clear any deferred bind-runnables from the synchronized load process
1295 // We must do this before any loading/binding is scheduled below.
1296 synchronized(mDeferredBindRunnables) {
1297 mDeferredBindRunnables.clear();
1298 }
1299 // Don't bother to start the thread if we know it's not going to do anything
1300 if ((mCallbacks != null) && (mCallbacks.get() != null)) {
1301 // If there is already one running, tell it to stop.
1302 stopLoaderLocked();
1303 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1304 if ((((synchronousBindPage != PagedView.INVALID_RESTORE_PAGE) && mAllAppsLoaded) && mWork🔵
1305 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1306 } else {
1307 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1308 sWorker.post(mLoaderTask);
1309 }
1310 }
1311 }
1312 }
1313
1314 void bindRemainingSynchronousPages() {
1315 // Post the remaining side pages to be loaded
1316 if (!mDeferredBindRunnables.isEmpty()) {
1317 Runnable[] deferredBindRunnables = null;
1318 synchronized(mDeferredBindRunnables) {
1319 deferredBindRunnables = mDeferredBindRunnables.toArray(new Runnable[mDeferredBindRunnable🔵
1320 mDeferredBindRunnables.clear();
1321 }
1322 for (final Runnable r : deferredBindRunnables) {
1323 mHandler.post(r);
1324 }
1325 }
1326 // Run all the bind complete runnables after workspace is bound.
1327 if (!mBindCompleteRunnables.isEmpty()) {
1328 synchronized(mBindCompleteRunnables) {
1329 for (final Runnable r : mBindCompleteRunnables) {
1330 runOnWorkerThread(r);
1331 }
1332 mBindCompleteRunnables.clear();
1333 }
1334 }
1335 }
1336
1337 public void stopLoader() {
1338 synchronized (mLock) {
1339 if (mLoaderTask != null) {
1340 mLoaderTask.stopLocked();
1341 }
1342 }
1343 }
1344
1345 /**
1346 * Loads the workspace screen ids in an ordered list.
1347 */
1348 @Thunk
1349 static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1350 final ContentResolver contentResolver = context.getContentResolver();
1351 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1352 // Get screens ordered by rank.
1353 final Cursor sc = contentResolver.query(screensUri, null, null, null, LauncherSettings.WorkspaceS🔵
1354 ArrayList<Long> screenIds = new ArrayList<Long>();
1355 try {
1356 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1357 while (sc.moveToNext()) {
1358 try {
1359 screenIds.add(sc.getLong(idIndex));
1360 } catch (java.lang.Exception e) {
1361 Launcher.addDumpLog(TAG, ("Desktop items loading interrupted" + " - invalid screens: 🔵
1362 }
1363 }
1364 } finally {
1365 sc.close();
1366 }
1367 return screenIds;
1368 }
1369
1370 public boolean isAllAppsLoaded() {
1371 return mAllAppsLoaded;
1372 }
1373
1374 boolean isLoadingWorkspace() {
1375 synchronized (mLock) {
1376 if (mLoaderTask != null) {
1377 return mLoaderTask.isLoadingWorkspace();
1378 }
1379 }
1380 return false;
1381 }
1382
1383 /**
1384 * Runnable for the thread that loads the contents of the launcher:
1385 * - workspace icons
1386 * - widgets
1387 * - all apps icons
1388 */
1389 private class LoaderTask implements Runnable {
1390 private Context mContext;
1391
1392 @Thunk
1393 boolean mIsLoadingAndBindingWorkspace;
1394
1395 private boolean mStopped;
1396
1397 @Thunk
1398 boolean mLoadAndBindStepFinished;
1399
1400 private int mFlags;
1401
1402 LoaderTask(Context context, int flags) {
1403 mContext = context;
1404 mFlags = flags;
1405 }
1406
1407 boolean isLoadingWorkspace() {
1408 return mIsLoadingAndBindingWorkspace;
1409 }
1410
1411 private void loadAndBindWorkspace() {
1412 mIsLoadingAndBindingWorkspace = true;
1413 // Load the workspace
1414 if (DEBUG_LOADERS) {
1415 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1416 }
1417 if (!mWorkspaceLoaded) {
1418 loadWorkspace();
1419 synchronized(this) {
1420 if (mStopped) {
1421 return;
1422 }
1423 mWorkspaceLoaded = true;
1424 }
1425 }
1426 // Bind the workspace
1427 bindWorkspace(-1);
1428 }
1429
1430 private void waitForIdle() {
1431 // Wait until the either we're stopped or the other threads are done.
1432 // This way we don't start loading all apps until the workspace has settled
1433 // down.
1434 synchronized (LoaderTask.this) {
1435 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1436
1437 mHandler.postIdle(new Runnable() {
1438 public void run() {
1439 synchronized (LoaderTask.this) {
1440 mLoadAndBindStepFinished = true;
1441 if (DEBUG_LOADERS) {
1442 Log.d(TAG, "done with previous binding step");
1443 }
1444 LoaderTask.this.notify();
1445 }
1446 }
1447 });
1448
1449 while (!mStopped && !mLoadAndBindStepFinished) {
1450 try {
1451 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1452 // wait no longer than 1sec at a time
1453 this.wait(1000);
1454 } catch (InterruptedException ex) {
1455 // Ignore
1456 }
1457 }
1458 if (DEBUG_LOADERS) {
1459 Log.d(TAG, "waited "
1460 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1461 + "ms for previous step to finish binding");
1462 }
1463 }
1464 }
1465
1466 void runBindSynchronousPage(int synchronousBindPage) {
1467 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1468 // Ensure that we have a valid page index to load synchronously
1469 throw new RuntimeException("Should not call runBindSynchronousPage() without " + "valid p🔵
1470 }
1471 if ((!mAllAppsLoaded) || (!mWorkspaceLoaded)) {
1472 // Ensure that we don't try and bind a specified page when the pages have not been
1473 // loaded already (we should load everything asynchronously in that case)
1474 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1475 }
1476 synchronized(mLock) {
1477 if (mIsLoaderTaskRunning) {
1478 // Ensure that we are never running the background loading at this point since
1479 // we also touch the background collections
1480 throw new RuntimeException("Error! Background loading is already running");
1481 }
1482 }
1483 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1484 // data structures, we can't allow any other thread to touch that data, but because
1485 // this call is synchronous, we can get away with not locking).
1486 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1487 // operations from the previous activity. We need to ensure that all queued operations
1488 // are executed before any synchronous binding work is done.
1489 mHandler.flush();
1490 // Divide the set of loaded items into those that we are binding synchronously, and
1491 // everything else that is to be bound normally (asynchronously).
1492 bindWorkspace(synchronousBindPage);
1493 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1494 // arise from that.
1495 onlyBindAllApps();
1496 }
1497
1498 public void run() {
1499 synchronized(mLock) {
1500 if (mStopped) {
1501 return;
1502 }
1503 mIsLoaderTaskRunning = true;
1504 }
1505 // Optimize for end-user experience: if the Launcher is up and // running with the
1506 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1507 // workspace first (default).
1508 keep_running : {
1509 if (DEBUG_LOADERS) {
1510 Log.d(TAG, "step 1: loading workspace");
1511 }
1512 loadAndBindWorkspace();
1513 if (mStopped) {
1514 break keep_running;
1515 }
1516 waitForIdle();
1517 // second step
1518 if (DEBUG_LOADERS) {
1519 Log.d(TAG, "step 2: loading all apps");
1520 }
1521 loadAndBindAllApps();
1522 }
1523 // Clear out this reference, otherwise we end up holding it until all of the
1524 // callback runnables are done.
1525 mContext = null;
1526 synchronized(mLock) {
1527 // If we are still the last one to be scheduled, remove ourselves.
1528 if (mLoaderTask == this) {
1529 mLoaderTask = null;
1530 }
1531 mIsLoaderTaskRunning = false;
1532 mHasLoaderCompletedOnce = true;
1533 }
1534 }
1535
1536 public void stopLocked() {
1537 synchronized (LoaderTask.this) {
1538 mStopped = true;
1539 this.notify();
1540 }
1541 }
1542
1543 /**
1544 * Gets the callbacks object. If we've been stopped, or if the launcher object
1545 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1546 * object that was around when the deferred message was scheduled, and if there's
1547 * a new Callbacks object around then also return null. This will save us from
1548 * calling onto it with data that will be ignored.
1549 */
1550 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1551 synchronized (mLock) {
1552 if (mStopped) {
1553 return null;
1554 }
1555
1556 if (mCallbacks == null) {
1557 return null;
1558 }
1559
1560 final Callbacks callbacks = mCallbacks.get();
1561 if (callbacks != oldCallbacks) {
1562 return null;
1563 }
1564 if (callbacks == null) {
1565 Log.w(TAG, "no mCallbacks");
1566 return null;
1567 }
1568
1569 return callbacks;
1570 }
1571 }
1572
1573 // check & update map of what's occupied; used to discard overlapping/invalid items
1574 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1575 LauncherAppState app = LauncherAppState.getInstance();
1576 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1577 final int countX = ((int) (profile.numColumns));
1578 final int countY = ((int) (profile.numRows));
1579 long containerIndex = item.screenId;
1580 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1581 // Return early if we detect that an item is under the hotseat button
1582 if ((mCallbacks == null) || mCallbacks.get().isAllAppsButtonRank(((int) (item.screenId)))🔵
1583 Log.e(TAG, ((((((("Error loading shortcut into hotseat " + item) + " into position ("🔵
1584 return false;
1585 }
1586 final ItemInfo[][] hotseatItems = occupied.get(((long) (LauncherSettings.Favorites.CONTAI🔵
1587 if (item.screenId >= profile.numHotseatIcons) {
1588 Log.e(TAG, ((((("Error loading shortcut " + item) + " into hotseat position ") + item🔵
1589 return false;
1590 }
1591 if (hotseatItems != null) {
1592 if (hotseatItems[((int) (item.screenId))][0] != null) {
1593 Log.e(TAG, (((((((("Error loading shortcut into hotseat " + item) + " into positi🔵
1594 return false;
1595 } else {
1596 hotseatItems[((int) (item.screenId))][0] = item;
1597 return true;
1598 }
1599 } else {
1600 final ItemInfo[][] items = new ItemInfo[((int) (profile.numHotseatIcons))][1];
1601 items[((int) (item.screenId))][0] = item;
1602 occupied.put(((long) (LauncherSettings.Favorites.CONTAINER_HOTSEAT)), items);
1603 return true;
1604 }
1605 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1606 // Skip further checking if it is not the hotseat or workspace container
1607 return true;
1608 }
1609 if (!occupied.containsKey(item.screenId)) {
1610 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1611 occupied.put(item.screenId, items);
1612 }
1613 final ItemInfo[][] screens = occupied.get(item.screenId);
1614 if (((((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && (item.cellX < 0)) 🔵
1615 Log.e(TAG, ((((((((((((("Error loading shortcut " + item) + " into cell (") + containerIn🔵
1616 return false;
1617 }
1618 // Check if any workspace icons overlap with each other
1619 for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
1620 for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
1621 if (screens[x][y] != null) {
1622 Log.e(TAG, (((((((((("Error loading shortcut " + item) + " into cell (") + contai🔵
1623 return false;
1624 }
1625 }
1626 }
1627 for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
1628 for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
1629 screens[x][y] = item;
1630 }
1631 }
1632 return true;
1633 }
1634
1635 /** Clears all the sBg data structures */
1636 private void clearSBgDataStructures() {
1637 synchronized(sBgLock) {
1638 sBgWorkspaceItems.clear();
1639 sBgAppWidgets.clear();
1640 sBgFolders.clear();
1641 sBgItemsIdMap.clear();
1642 sBgWorkspaceScreens.clear();
1643 }
1644 }
1645
1646 private void loadWorkspace() {
1647 final long t = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
1648 final Context context = mContext;
1649 final ContentResolver contentResolver = context.getContentResolver();
1650 final PackageManager manager = context.getPackageManager();
1651 final boolean isSafeMode = manager.isSafeMode();
1652 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1653 final boolean isSdCardReady = context.registerReceiver(null, new IntentFilter(StartupReceiver🔵
1654 LauncherAppState app = LauncherAppState.getInstance();
1655 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1656 int countX = ((int) (profile.numColumns));
1657 int countY = ((int) (profile.numRows));
1658 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1659 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1660 LauncherAppState.getLauncherProvider().deleteDatabase();
1661 }
1662 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1663 // append the user's Launcher2 shortcuts
1664 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1665 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1666 } else {
1667 // Make sure the default workspace is loaded
1668 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1669 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1670 }
1671 synchronized(sBgLock) {
1672 clearSBgDataStructures();
1673 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat.getInstance(mConte🔵
1674 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1675 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1676 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1677 if (DEBUG_LOADERS) {
1678 Log.d(TAG, "loading model from " + contentUri);
1679 }
1680 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1681 // +1 for the hotseat (it can be larger than the workspace)
1682 // Load workspace in reverse order to ensure that latest items are loaded first (and
1683 // before any earlier duplicates)
1684 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1685 try {
1686 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1687 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1688 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1689 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAIN🔵
1690 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYP🔵
1691 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWI🔵
1692 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites🔵
1693 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1694 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1695 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1696 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
1697 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
1698 final int rankIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RANK);
1699 final int restoredIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED🔵
1700 final int profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE🔵
1701 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
1702 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1703 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1704 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1705 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1706 }
1707 ShortcutInfo info;
1708 String intentDescription;
1709 LauncherAppWidgetInfo appWidgetInfo;
1710 int container;
1711 long id;
1712 long serialNumber;
1713 Intent intent;
1714 UserHandleCompat user;
1715 while ((!mStopped) && c.moveToNext()) {
1716 try {
1717 int itemType = c.getInt(itemTypeIndex);
1718 boolean restored = 0 != c.getInt(restoredIndex);
1719 boolean allowMissingTarget = false;
1720 container = c.getInt(containerIndex);
1721 switch (itemType) {
1722 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1723 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1724 id = c.getLong(idIndex);
1725 intentDescription = c.getString(intentIndex);
1726 serialNumber = c.getInt(profileIdIndex);
1727 user = allUsers.get(serialNumber);
1728 int promiseType = c.getInt(restoredIndex);
1729 int disabledState = 0;
1730 boolean itemReplaced = false;
1731 if (user == null) {
1732 // User has been deleted remove the item.
1733 itemsToRemove.add(id);
1734 continue;
1735 }
1736 try {
1737 intent = Intent.parseUri(intentDescription, 0);
1738 ComponentName cn = intent.getComponent();
1739 if ((cn != null) && (cn.getPackageName() != null)) {
1740 boolean validPkg = launcherApps.isPackageEnabledForProfile(cn🔵
1741 boolean validComponent = validPkg && launcherApps.isActivityE🔵
1742 if (validComponent) {
1743 if (restored) {
1744 // no special handling necessary for this item
1745 restoredRows.add(id);
1746 restored = false;
1747 }
1748 } else if (validPkg) {
1749 intent = null;
1750 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 🔵
1751 // We allow auto install apps to have their intent
1752 // updated after an install.
1753 intent = manager.getLaunchIntentForPackage(cn.getPack🔵
1754 if (intent != null) {
1755 ContentValues values = new ContentValues();
1756 values.put(LauncherSettings.Favorites.INTENT, int🔵
1757 updateItem(id, values);
1758 }
1759 }
1760 if (intent == null) {
1761 // The app is installed but the component is no
1762 // longer available.
1763 Launcher.addDumpLog(TAG, "Invalid component removed: 🔵
1764 itemsToRemove.add(id);
1765 continue;
1766 } else {
1767 // no special handling necessary for this item
1768 restoredRows.add(id);
1769 restored = false;
1770 }
1771 } else if (restored) {
1772 // Package is not yet available but might be
1773 // installed later.
1774 Launcher.addDumpLog(TAG, "package not yet restored: " + c🔵
1775 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 🔵
1776 // Restore has started once.
1777 } else if (installingPkgs.containsKey(cn.getPackageName()🔵
1778 // App restore has started. Update the flag
1779 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1780 ContentValues values = new ContentValues();
1781 values.put(LauncherSettings.Favorites.RESTORED, promi🔵
1782 updateItem(id, values);
1783 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_🔵
1784 // This is a common app. Try to replace this.
1785 int appType = CommonAppTypeParser.decodeItemTypeFromF🔵
1786 CommonAppTypeParser parser = new CommonAppTypeParser(🔵
1787 if (parser.findDefaultApp()) {
1788 // Default app found. Replace it.
1789 intent = parser.parsedIntent;
1790 cn = intent.getComponent();
1791 ContentValues values = parser.parsedValues;
1792 values.put(LauncherSettings.Favorites.RESTORED, 0🔵
1793 updateItem(id, values);
1794 restored = false;
1795 itemReplaced = true;
1796 } else if (REMOVE_UNRESTORED_ICONS) {
1797 Launcher.addDumpLog(TAG, "Unrestored package remo🔵
1798 itemsToRemove.add(id);
1799 continue;
1800 }
1801 } else if (REMOVE_UNRESTORED_ICONS) {
1802 Launcher.addDumpLog(TAG, "Unrestored package removed:🔵
1803 itemsToRemove.add(id);
1804 continue;
1805 }
1806 } else if (launcherApps.isAppEnabled(manager, cn.getPackageNa🔵
1807 // Package is present but not available.
1808 allowMissingTarget = true;
1809 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1810 } else if (!isSdCardReady) {
1811 // SdCard is not ready yet. Package might get available,
1812 // once it is ready.
1813 Launcher.addDumpLog(TAG, ("Invalid package: " + cn) + " (🔵
1814 HashSet<String> pkgs = sPendingPackages.get(user);
1815 if (pkgs == null) {
1816 pkgs = new HashSet<String>();
1817 sPendingPackages.put(user, pkgs);
1818 }
1819 pkgs.add(cn.getPackageName());
1820 allowMissingTarget = true;
1821 // Add the icon on the workspace anyway.
1822 } else {
1823 // Do not wait for external media load anymore.
1824 // Log the invalid package, and remove it
1825 Launcher.addDumpLog(TAG, "Invalid package removed: " + cn🔵
1826 itemsToRemove.add(id);
1827 continue;
1828 }
1829 } else if (cn == null) {
1830 // For shortcuts with no component, keep them as they are
1831 restoredRows.add(id);
1832 restored = false;
1833 }
1834 } catch (URISyntaxException e) {
1835 Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, tru🔵
1836 continue;
1837 }
1838 boolean useLowResIcon = (container >= 0) && (c.getInt(rankIndex) >= F🔵
1839 if (itemReplaced) {
1840 if (user.equals(UserHandleCompat.myUserHandle())) {
1841 info = getAppShortcutInfo(manager, intent, user, context, nul🔵
1842 } else {
1843 // Don't replace items for other profiles.
1844 itemsToRemove.add(id);
1845 continue;
1846 }
1847 } else if (restored) {
1848 if (user.equals(UserHandleCompat.myUserHandle())) {
1849 Launcher.addDumpLog(TAG, "constructing info for partially res🔵
1850 info = getRestoredItemInfo(c, titleIndex, intent, promiseType🔵
1851 intent = getRestoredItemIntent(c, context, intent);
1852 } else {
1853 // Don't restore items for other profiles.
1854 itemsToRemove.add(id);
1855 continue;
1856 }
1857 } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATI🔵
1858 info = getAppShortcutInfo(manager, intent, user, context, c, curs🔵
1859 } else {
1860 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
1861 // App shortcuts that used to be automatically added to Launcher
1862 // didn't always have the correct intent flags set, so do that
1863 // here
1864 if ((((intent.getAction() != null) && (intent.getCategories() != 🔵
1865 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_A🔵
1866 }
1867 }
1868 if (info != null) {
1869 info.id = id;
1870 info.intent = intent;
1871 info.container = container;
1872 info.screenId = c.getInt(screenIndex);
1873 info.cellX = c.getInt(cellXIndex);
1874 info.cellY = c.getInt(cellYIndex);
1875 info.rank = c.getInt(rankIndex);
1876 info.spanX = 1;
1877 info.spanY = 1;
1878 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1879 if (info.promisedIntent != null) {
1880 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNu🔵
1881 }
1882 info.isDisabled = disabledState;
1883 if (isSafeMode && (!Utilities.isSystemApp(context, intent))) {
1884 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1885 }
1886 // check & update map of what's occupied
1887 if (!checkItemPlacement(occupied, info)) {
1888 itemsToRemove.add(id);
1889 break;
1890 }
1891 if (restored) {
1892 ComponentName cn = info.getTargetComponent();
1893 if (cn != null) {
1894 Integer progress = installingPkgs.get(cn.getPackageName()🔵
1895 if (progress != null) {
1896 info.setInstallProgress(progress);
1897 } else {
1898 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACT🔵
1899 }
1900 }
1901 }
1902 switch (container) {
1903 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1904 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1905 sBgWorkspaceItems.add(info);
1906 break;
1907 default :
1908 // Item is in a user folder
1909 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, cont🔵
1910 folderInfo.add(info);
1911 break;
1912 }
1913 sBgItemsIdMap.put(info.id, info);
1914 } else {
1915 throw new RuntimeException("Unexpected null ShortcutInfo");
1916 }
1917 break;
1918 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1919 id = c.getLong(idIndex);
1920 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
1921 // Do not trim the folder label, as is was set by the user.
1922 folderInfo.title = c.getString(titleIndex);
1923 folderInfo.id = id;
1924 folderInfo.container = container;
1925 folderInfo.screenId = c.getInt(screenIndex);
1926 folderInfo.cellX = c.getInt(cellXIndex);
1927 folderInfo.cellY = c.getInt(cellYIndex);
1928 folderInfo.spanX = 1;
1929 folderInfo.spanY = 1;
1930 folderInfo.options = c.getInt(optionsIndex);
1931 // check & update map of what's occupied
1932 if (!checkItemPlacement(occupied, folderInfo)) {
1933 itemsToRemove.add(id);
1934 break;
1935 }
1936 switch (container) {
1937 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1938 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1939 sBgWorkspaceItems.add(folderInfo);
1940 break;
1941 }
1942 if (restored) {
1943 // no special handling required for restored folders
1944 restoredRows.add(id);
1945 }
1946 sBgItemsIdMap.put(folderInfo.id, folderInfo);
1947 sBgFolders.put(folderInfo.id, folderInfo);
1948 break;
1949 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1950 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1951 // Read all Launcher-specific widget details
1952 boolean customWidget = itemType ==
1953 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1954
1955 int appWidgetId = c.getInt(appWidgetIdIndex);
1956
1957 <<<<<<< LEFT
1958 serialNumber = c.getLong(profileIdIndex);
1959
1960 ||||||| BASE
1961 /*d94z9sk0k4hf9j3ijd - note the base isn't actually empty, spork simply doesn't generate a base - gd930kw🔵
1962 =======
1963
1964 serialNumber= c.getLong(profileIdIndex);
1965 user = mUserManager.getUserForSerialNumber(serialNumber);
1966 if (user == null) {
1967 // User has been deleted remove the item.
1968 itemsToRemove.add(id);
1969 continue;
1970 }
1971
1972 >>>>>>> RIGHT
1973 String savedProvider = c.getString(appWidgetProviderIndex);
1974 id = c.getLong(idIndex);
1975 user = allUsers.get(serialNumber);
1976 if (user == null) {
1977 itemsToRemove.add(id);
1978 continue;
1979 }
1980
1981 final ComponentName component =
1982 ComponentName.unflattenFromString(savedProvider);
1983
1984 final int restoreStatus = c.getInt(restoredIndex);
1985 final boolean isIdValid = (restoreStatus &
1986 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
1987 final boolean wasProviderReady = (restoreStatus &
1988 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
1989
1990 final LauncherAppWidgetProviderInfo provider =
1991 LauncherModel.getProviderInfo(context,
1992 ComponentName.unflattenFromString(savedProvider),
1993 user);
1994
1995 final boolean isProviderReady = isValidProvider(provider);
1996 if (!isSafeMode && !customWidget &&
1997 wasProviderReady && !isProviderReady) {
1998 String log = "Deleting widget that isn't installed anymore: "
1999 + "id=" + id + " appWidgetId=" + appWidgetId;
2000
2001 Log.e(TAG, log);
2002 Launcher.addDumpLog(TAG, log, false);
2003 itemsToRemove.add(id);
2004 } else {
2005 if (isProviderReady) {
2006 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2007 provider.provider);
2008
2009 int status = restoreStatus;
2010 if (!wasProviderReady) {
2011 // If provider was not previously ready, update the
2012 // status and UI flag.
2013
2014 // Id would be valid only if the widget restore broadcast was🔵
2015 if (isIdValid) {
2016 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2017 } else {
2018 status &= ~LauncherAppWidgetInfo
2019 .FLAG_PROVIDER_NOT_READY;
2020 }
2021 }
2022 appWidgetInfo.restoreStatus = status;
2023 } else {
2024 Log.v(TAG, "Widget restore pending id=" + id
2025 + " appWidgetId=" + appWidgetId
2026 + " status =" + restoreStatus);
2027 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2028 component);
2029 appWidgetInfo.restoreStatus = restoreStatus;
2030 Integer installProgress = installingPkgs.get(component.getPackage🔵
2031
2032 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2033 // Restore has started once.
2034 } else if (installProgress != null) {
2035 // App restore has started. Update the flag
2036 appWidgetInfo.restoreStatus |=
2037 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2038 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2039 Launcher.addDumpLog(TAG,
2040 "Unrestored widget removed: " + component, true);
2041 itemsToRemove.add(id);
2042 continue;
2043 }
2044
2045 appWidgetInfo.installProgress =
2046 installProgress == null ? 0 : installProgress;
2047 }
2048
2049 appWidgetInfo.id = id;
2050 appWidgetInfo.screenId = c.getInt(screenIndex);
2051 appWidgetInfo.cellX = c.getInt(cellXIndex);
2052 appWidgetInfo.cellY = c.getInt(cellYIndex);
2053 appWidgetInfo.spanX = c.getInt(spanXIndex);
2054 appWidgetInfo.spanY = c.getInt(spanYIndex);
2055 appWidgetInfo.user = user;
2056
2057 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2058 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2059 Log.e(TAG, "Widget found where container != " +
2060 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2061 continue;
2062 }
2063
2064 appWidgetInfo.container = container;
2065 // check & update map of what's occupied
2066 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2067 itemsToRemove.add(id);
2068 break;
2069 }
2070
2071 if (!customWidget) {
2072 String providerName =
2073 appWidgetInfo.providerName.flattenToString();
2074 if (!providerName.equals(savedProvider) ||
2075 (appWidgetInfo.restoreStatus != restoreStatus)) {
2076 ContentValues values = new ContentValues();
2077 values.put(
2078 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2079 providerName);
2080 values.put(LauncherSettings.Favorites.RESTORED,
2081 appWidgetInfo.restoreStatus);
2082 updateItem(id, values);
2083 }
2084 }
2085 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2086 sBgAppWidgets.add(appWidgetInfo);
2087 }
2088 break;
2089 }
2090 } catch (java.lang.Exception e) {
2091 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2092 }
2093 }
2094 } finally {
2095 if (c != null) {
2096 c.close();
2097 }
2098 }
2099 // Break early if we've stopped loading
2100 if (mStopped) {
2101 clearSBgDataStructures();
2102 return;
2103 }
2104 if (itemsToRemove.size() > 0) {
2105 // Remove dead items
2106 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI, Utilities.createDbSele🔵
2107 if (DEBUG_LOADERS) {
2108 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(LauncherSettings.Favor🔵
2109 }
2110 // Remove any empty folder
2111 for (long folderId : LauncherAppState.getLauncherProvider().deleteEmptyFolders()) {
2112 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2113 sBgFolders.remove(folderId);
2114 sBgItemsIdMap.remove(folderId);
2115 }
2116 }
2117 if (restoredRows.size() > 0) {
2118 // Update restored items that no longer require special handling
2119 ContentValues values = new ContentValues();
2120 values.put(LauncherSettings.Favorites.RESTORED, 0);
2121 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values, Utilities.crea🔵
2122 }
2123 if ((!isSdCardReady) && (!sPendingPackages.isEmpty())) {
2124 context.registerReceiver(new AppsAvailabilityCheck(), new IntentFilter(StartupReceive🔵
2125 }
2126 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2127 // Remove any empty screens
2128 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2129 for (ItemInfo item : sBgItemsIdMap) {
2130 long screenId = item.screenId;
2131 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && unusedScreens🔵
2132 unusedScreens.remove(screenId);
2133 }
2134 }
2135 // If there are any empty screens remove them, and update.
2136 if (unusedScreens.size() != 0) {
2137 sBgWorkspaceScreens.removeAll(unusedScreens);
2138 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2139 }
2140 if (DEBUG_LOADERS) {
2141 Log.d(TAG, ("loaded workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2142 Log.d(TAG, "workspace layout: ");
2143 int nScreens = occupied.size();
2144 for (int y = 0; y < countY; y++) {
2145 String line = "";
2146 for (int i = 0; i < nScreens; i++) {
2147 long screenId = occupied.keyAt(i);
2148 if (screenId > 0) {
2149 line += " | ";
2150 }
2151 ItemInfo[][] screen = occupied.valueAt(i);
2152 for (int x = 0; x < countX; x++) {
2153 if ((x < screen.length) && (y < screen[x].length)) {
2154 line += (screen[x][y] != null) ? "#" : ".";
2155 } else {
2156 line += "!";
2157 }
2158 }
2159 }
2160 Log.d(TAG, ("[ " + line) + " ]");
2161 }
2162 }
2163 }
2164 }
2165
2166 /**
2167 * Partially updates the item without any notification. Must be called on the worker thread.
2168 */
2169 private void updateItem(long itemId, ContentValues update) {
2170 mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, update, BaseColu🔵
2171 }
2172
2173 /** Filters the set of items who are directly or indirectly (via another container) on the
2174 * specified screen. */
2175 private void filterCurrentWorkspaceItems(long currentScreenId,
2176 ArrayList<ItemInfo> allWorkspaceItems,
2177 ArrayList<ItemInfo> currentScreenItems,
2178 ArrayList<ItemInfo> otherScreenItems) {
2179 // Purge any null ItemInfos
2180 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2181 while (iter.hasNext()) {
2182 ItemInfo i = iter.next();
2183 if (i == null) {
2184 iter.remove();
2185 }
2186 }
2187
2188 // Order the set of items by their containers first, this allows use to walk through the
2189 // list sequentially, build up a list of containers that are in the specified screen,
2190 // as well as all items in those containers.
2191 Set<Long> itemsOnScreen = new HashSet<Long>();
2192 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2193 @Override
2194 public int compare(ItemInfo lhs, ItemInfo rhs) {
2195 return (int) (lhs.container - rhs.container);
2196 }
2197 });
2198 for (ItemInfo info : allWorkspaceItems) {
2199 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2200 if (info.screenId == currentScreenId) {
2201 currentScreenItems.add(info);
2202 itemsOnScreen.add(info.id);
2203 } else {
2204 otherScreenItems.add(info);
2205 }
2206 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2207 currentScreenItems.add(info);
2208 itemsOnScreen.add(info.id);
2209 } else {
2210 if (itemsOnScreen.contains(info.container)) {
2211 currentScreenItems.add(info);
2212 itemsOnScreen.add(info.id);
2213 } else {
2214 otherScreenItems.add(info);
2215 }
2216 }
2217 }
2218 }
2219
2220 /** Filters the set of widgets which are on the specified screen. */
2221 private void filterCurrentAppWidgets(long currentScreenId,
2222 ArrayList<LauncherAppWidgetInfo> appWidgets,
2223 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2224 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2225
2226 for (LauncherAppWidgetInfo widget : appWidgets) {
2227 if (widget == null) continue;
2228 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2229 widget.screenId == currentScreenId) {
2230 currentScreenWidgets.add(widget);
2231 } else {
2232 otherScreenWidgets.add(widget);
2233 }
2234 }
2235 }
2236
2237 /** Filters the set of folders which are on the specified screen. */
2238 private void filterCurrentFolders(long currentScreenId, LongArrayMap<ItemInfo> itemsIdMap, LongAr🔵
2239 int total = folders.size();
2240 for (int i = 0; i < total; i++) {
2241 long id = folders.keyAt(i);
2242 FolderInfo folder = folders.valueAt(i);
2243 ItemInfo info = itemsIdMap.get(id);
2244 if ((info == null) || (folder == null)) {
2245 continue;
2246 }
2247 if ((info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && (info.screenId ==🔵
2248 currentScreenFolders.put(id, folder);
2249 } else {
2250 otherScreenFolders.put(id, folder);
2251 }
2252 }
2253 }
2254
2255 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2256 * right) */
2257 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2258 final LauncherAppState app = LauncherAppState.getInstance();
2259 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2260 // XXX: review this
2261 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2262 @Override
2263 public int compare(ItemInfo lhs, ItemInfo rhs) {
2264 int cellCountX = ((int) (profile.numColumns));
2265 int cellCountY = ((int) (profile.numRows));
2266 int screenOffset = cellCountX * cellCountY;
2267 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1);// +1 hotseat
2268
2269 long lr = (((lhs.container * containerOffset) + (lhs.screenId * screenOffset)) + (lhs🔵
2270 long rr = (((rhs.container * containerOffset) + (rhs.screenId * screenOffset)) + (rhs🔵
2271 return ((int) (lr - rr));
2272 }
2273 });
2274 }
2275
2276 private void bindWorkspaceScreens(final Callbacks oldCallbacks, final ArrayList<Long> orderedScre🔵
2277 final Runnable r = new Runnable() {
2278 @Override
2279 public void run() {
2280 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2281 if (callbacks != null) {
2282 callbacks.bindScreens(orderedScreens);
2283 }
2284 }
2285 };
2286 runOnMainThread(r);
2287 }
2288
2289 private void bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspace🔵
2290 final boolean postOnMainThread = deferredBindRunnables != null;
2291 // Bind the workspace items
2292 int N = workspaceItems.size();
2293 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2294 final int start = i;
2295 final int chunkSize = ((i + ITEMS_CHUNK) <= N) ? ITEMS_CHUNK : N - i;
2296 final Runnable r = new Runnable() {
2297 @Override
2298 public void run() {
2299 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2300 if (callbacks != null) {
2301 callbacks.bindItems(workspaceItems, start, start + chunkSize, false);
2302 }
2303 }
2304 };
2305 if (postOnMainThread) {
2306 synchronized(deferredBindRunnables) {
2307 deferredBindRunnables.add(r);
2308 }
2309 } else {
2310 runOnMainThread(r);
2311 }
2312 }
2313 // Bind the folders
2314 if (!folders.isEmpty()) {
2315 final Runnable r = new Runnable() {
2316 public void run() {
2317 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2318 if (callbacks != null) {
2319 callbacks.bindFolders(folders);
2320 }
2321 }
2322 };
2323 if (postOnMainThread) {
2324 synchronized(deferredBindRunnables) {
2325 deferredBindRunnables.add(r);
2326 }
2327 } else {
2328 runOnMainThread(r);
2329 }
2330 }
2331 // Bind the widgets, one at a time
2332 N = appWidgets.size();
2333 for (int i = 0; i < N; i++) {
2334 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2335 final Runnable r = new Runnable() {
2336 public void run() {
2337 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2338 if (callbacks != null) {
2339 callbacks.bindAppWidget(widget);
2340 }
2341 }
2342 };
2343 if (postOnMainThread) {
2344 deferredBindRunnables.add(r);
2345 } else {
2346 runOnMainThread(r);
2347 }
2348 }
2349 }
2350
2351 /**
2352 * Binds all loaded data to actual views on the main thread.
2353 */
2354 private void bindWorkspace(int synchronizeBindPage) {
2355 final long t = SystemClock.uptimeMillis();
2356 Runnable r;
2357 // Don't use these two variables in any of the callback runnables.
2358 // Otherwise we hold a reference to them.
2359 final Callbacks oldCallbacks = mCallbacks.get();
2360 if (oldCallbacks == null) {
2361 // This launcher has exited and nobody bothered to tell us. Just bail.
2362 Log.w(TAG, "LoaderTask running with no launcher");
2363 return;
2364 }
2365 // Save a copy of all the bg-thread collections
2366 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2367 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>();
2368 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2369 final LongArrayMap<FolderInfo> folders;
2370 final LongArrayMap<ItemInfo> itemsIdMap;
2371 synchronized(sBgLock) {
2372 workspaceItems.addAll(sBgWorkspaceItems);
2373 appWidgets.addAll(sBgAppWidgets);
2374 orderedScreenIds.addAll(sBgWorkspaceScreens);
2375 folders = sBgFolders.clone();
2376 itemsIdMap = sBgItemsIdMap.clone();
2377 }
2378 final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2379 int currScreen = (isLoadingSynchronously) ? synchronizeBindPage : oldCallbacks.getCurrentWork🔵
2380 if (currScreen >= orderedScreenIds.size()) {
2381 // There may be no workspace screens (just hotseat items and an empty page).
2382 currScreen = PagedView.INVALID_RESTORE_PAGE;
2383 }
2384 final int currentScreen = currScreen;
2385 final long currentScreenId = (currentScreen < 0) ? INVALID_SCREEN_ID : orderedScreenIds.get(c🔵
2386 // Load all the items that are on the current page first (and in the process, unbind
2387 // all the existing workspace items before we call startBinding() below.
2388 unbindWorkspaceItemsOnMainThread();
2389 // Separate the items that are on the current screen, and all the other remaining items
2390 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2391 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2392 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
2393 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
2394 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2395 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2396 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWork🔵
2397 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);
2398 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);
2399 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2400 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2401 // Tell the workspace that we're about to start binding items
2402 r = new Runnable() {
2403 public void run() {
2404 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2405 if (callbacks != null) {
2406 callbacks.startBinding();
2407 }
2408 }
2409 };
2410 runOnMainThread(r);
2411 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2412 // Load items on the current page
2413 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, nu🔵
2414 if (isLoadingSynchronously) {
2415 r = new Runnable() {
2416 public void run() {
2417 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2418 if ((callbacks != null) && (currentScreen != PagedView.INVALID_RESTORE_PAGE)) {
2419 callbacks.onPageBoundSynchronously(currentScreen);
2420 }
2421 }
2422 };
2423 runOnMainThread(r);
2424 }
2425 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2426 // work until after the first render)
2427 synchronized(mDeferredBindRunnables) {
2428 mDeferredBindRunnables.clear();
2429 }
2430 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, isLoadin🔵
2431 // Tell the workspace that we're done binding items
2432 r = new Runnable() {
2433 public void run() {
2434 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2435 if (callbacks != null) {
2436 callbacks.finishBindingItems();
2437 }
2438 // If we're profiling, ensure this is the last thing in the queue.
2439 if (DEBUG_LOADERS) {
2440 Log.d(TAG, ("bound workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2441 }
2442 mIsLoadingAndBindingWorkspace = false;
2443 }
2444 };
2445 if (isLoadingSynchronously) {
2446 synchronized(mDeferredBindRunnables) {
2447 mDeferredBindRunnables.add(r);
2448 }
2449 } else {
2450 runOnMainThread(r);
2451 }
2452 }
2453
2454 private void loadAndBindAllApps() {
2455 if (DEBUG_LOADERS) {
2456 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2457 }
2458 if (!mAllAppsLoaded) {
2459 loadAllApps();
2460 synchronized(this) {
2461 if (mStopped) {
2462 return;
2463 }
2464 }
2465 updateIconCache();
2466 synchronized(this) {
2467 if (mStopped) {
2468 return;
2469 }
2470 mAllAppsLoaded = true;
2471 }
2472 } else {
2473 onlyBindAllApps();
2474 }
2475 }
2476
2477 private void updateIconCache() {
2478 // Ignore packages which have a promise icon.
2479 HashSet<String> packagesToIgnore = new HashSet<>();
2480 synchronized(sBgLock) {
2481 for (ItemInfo info : sBgItemsIdMap) {
2482 if (info instanceof ShortcutInfo) {
2483 ShortcutInfo si = ((ShortcutInfo) (info));
2484 if (si.isPromise() && (si.getTargetComponent() != null)) {
2485 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2486 }
2487 } else if (info instanceof LauncherAppWidgetInfo) {
2488 LauncherAppWidgetInfo lawi = ((LauncherAppWidgetInfo) (info));
2489 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2490 packagesToIgnore.add(lawi.providerName.getPackageName());
2491 }
2492 }
2493 }
2494 }
2495 mIconCache.updateDbIcons(packagesToIgnore);
2496 }
2497
2498 private void onlyBindAllApps() {
2499 final Callbacks oldCallbacks = mCallbacks.get();
2500 if (oldCallbacks == null) {
2501 // This launcher has exited and nobody bothered to tell us. Just bail.
2502 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2503 return;
2504 }
2505 // shallow copy
2506 @SuppressWarnings("unchecked")
2507 final ArrayList<AppInfo> list = ((ArrayList<AppInfo>) (mBgAllAppsList.data.clone()));
2508 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2509 Runnable r = new Runnable() {
2510 public void run() {
2511 final long t = SystemClock.uptimeMillis();
2512 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2513 if (callbacks != null) {
2514 callbacks.bindAllApplications(list);
2515 callbacks.bindAllPackages(widgetList);
2516 }
2517 if (DEBUG_LOADERS) {
2518 Log.d(TAG, ((("bound all " + list.size()) + " apps from cache in ") + (SystemCloc🔵
2519 }
2520 }
2521 };
2522 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2523 if (isRunningOnMainThread) {
2524 r.run();
2525 } else {
2526 mHandler.post(r);
2527 }
2528 }
2529
2530 private void loadAllApps() {
2531 final long loadTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2532 final Callbacks oldCallbacks = mCallbacks.get();
2533 if (oldCallbacks == null) {
2534 // This launcher has exited and nobody bothered to tell us. Just bail.
2535 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2536 return;
2537 }
2538 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2539 // Clear the list of apps
2540 mBgAllAppsList.clear();
2541 for (UserHandleCompat user : profiles) {
2542 // Query for the set of apps
2543 final long qiaTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2544 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2545 if (DEBUG_LOADERS) {
2546 Log.d(TAG, (("getActivityList took " + (SystemClock.uptimeMillis() - qiaTime)) + "ms 🔵
2547 Log.d(TAG, (("getActivityList got " + apps.size()) + " apps for user ") + user);
2548 }
2549 // Fail if we don't have any apps
2550 // TODO: Fix this. Only fail for the current user.
2551 if ((apps == null) || apps.isEmpty()) {
2552 return;
2553 }
2554 // Create the ApplicationInfos
2555 for (int i = 0; i < apps.size(); i++) {
2556 LauncherActivityInfoCompat app = apps.get(i);
2557 // This builds the icon bitmaps.
2558 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2559 }
2560 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2561 if (heuristic != null) {
2562 runAfterBindCompletes(new Runnable() {
2563 @Override
2564 public void run() {
2565 heuristic.processUserApps(apps);
2566 }
2567 });
2568 }
2569 }
2570 // Huh? Shouldn't this be inside the Runnable below?
2571 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2572 mBgAllAppsList.added = new ArrayList<AppInfo>();
2573 // Post callback on main thread
2574 mHandler.post(new Runnable() {
2575 public void run() {
2576 final long bindTime = SystemClock.uptimeMillis();
2577 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2578 if (callbacks != null) {
2579 callbacks.bindAllApplications(added);
2580 if (DEBUG_LOADERS) {
2581 Log.d(TAG, ((("bound " + added.size()) + " apps in ") + (SystemClock.uptimeMi🔵
2582 }
2583 } else {
2584 Log.i(TAG, "not binding apps: no Launcher activity");
2585 }
2586 }
2587 });
2588 // Cleanup any data stored for a deleted user.
2589 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2590 /* refresh */
2591 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks), true);
2592 if (DEBUG_LOADERS) {
2593 Log.d(TAG, ("Icons processed in " + (SystemClock.uptimeMillis() - loadTime)) + "ms");
2594 }
2595 }
2596
2597 public void dumpState() {
2598 synchronized(sBgLock) {
2599 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2600 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2601 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2602 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2603 }
2604 }
2605 }
2606
2607 /**
2608 * Called when the icons for packages have been updated in the icon cache.
2609 */
2610 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2611 final Callbacks callbacks = getCallback();
2612 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2613 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2614 // If any package icon has changed (app was updated while launcher was dead),
2615 // update the corresponding shortcuts.
2616 synchronized(sBgLock) {
2617 for (ItemInfo info : sBgItemsIdMap) {
2618 if (((info instanceof ShortcutInfo) && user.equals(info.user)) && (info.itemType == Launc🔵
2619 ShortcutInfo si = ((ShortcutInfo) (info));
2620 ComponentName cn = si.getTargetComponent();
2621 if ((cn != null) && updatedPackages.contains(cn.getPackageName())) {
2622 si.updateIcon(mIconCache);
2623 updatedShortcuts.add(si);
2624 }
2625 }
2626 }
2627 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2628 }
2629 if (!updatedShortcuts.isEmpty()) {
2630 final UserHandleCompat userFinal = user;
2631 mHandler.post(new Runnable() {
2632 public void run() {
2633 Callbacks cb = getCallback();
2634 if ((cb != null) && (callbacks == cb)) {
2635 cb.bindShortcutsChanged(updatedShortcuts, new ArrayList<ShortcutInfo>(), userFina🔵
2636 }
2637 }
2638 });
2639 }
2640 if (!updatedApps.isEmpty()) {
2641 mHandler.post(new Runnable() {
2642 public void run() {
2643 Callbacks cb = getCallback();
2644 if ((cb != null) && (callbacks == cb)) {
2645 cb.bindAppsUpdated(updatedApps);
2646 }
2647 }
2648 });
2649 }
2650 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2651 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
2652 }
2653
2654 void enqueuePackageUpdated(PackageUpdatedTask task) {
2655 sWorker.post(task);
2656 }
2657
2658 @Thunk
2659 class AppsAvailabilityCheck extends BroadcastReceiver {
2660 @Override
2661 public void onReceive(Context context, Intent intent) {
2662 synchronized (sBgLock) {
2663 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2664 .getInstance(mApp.getContext());
2665 final PackageManager manager = context.getPackageManager();
2666 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2667 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2668 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2669 UserHandleCompat user = entry.getKey();
2670 packagesRemoved.clear();
2671 packagesUnavailable.clear();
2672 for (String pkg : entry.getValue()) {
2673 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2674 boolean packageOnSdcard = launcherApps.isAppEnabled(
2675 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2676 if (packageOnSdcard) {
2677 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2678 packagesUnavailable.add(pkg);
2679 } else {
2680 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2681 packagesRemoved.add(pkg);
2682 }
2683 }
2684 }
2685 if (!packagesRemoved.isEmpty()) {
2686 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2687 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2688 }
2689 if (!packagesUnavailable.isEmpty()) {
2690 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2691 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2692 }
2693 }
2694 sPendingPackages.clear();
2695 }
2696 }
2697 }
2698
2699 private class PackageUpdatedTask implements Runnable {
2700 int mOp;
2701
2702 String[] mPackages;
2703
2704 UserHandleCompat mUser;
2705
2706 public static final int OP_NONE = 0;
2707
2708 public static final int OP_ADD = 1;
2709
2710 public static final int OP_UPDATE = 2;
2711
2712 // uninstlled
2713 public static final int OP_REMOVE = 3; // uninstlled
2714
2715 // external media unmounted
2716 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2717
2718 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2719 mOp = op;
2720 mPackages = packages;
2721 mUser = user;
2722 }
2723
2724 public void run() {
2725 if (!mHasLoaderCompletedOnce) {
2726 // Loader has not yet run.
2727 return;
2728 }
2729 final Context context = mApp.getContext();
2730 final String[] packages = mPackages;
2731 final int N = packages.length;
2732 switch (mOp) {
2733 case OP_ADD :
2734 {
2735 for (int i = 0; i < N; i++) {
2736 if (DEBUG_LOADERS) {
2737 Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2738 }
2739 mIconCache.updateIconsForPkg(packages[i], mUser);
2740 mBgAllAppsList.addPackage(context, packages[i], mUser);
2741 }
2742 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2743 if (heuristic != null) {
2744 heuristic.processPackageAdd(mPackages);
2745 }
2746 break;
2747 }
2748 case OP_UPDATE :
2749 for (int i = 0; i < N; i++) {
2750 if (DEBUG_LOADERS) {
2751 Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2752 }
2753 mIconCache.updateIconsForPkg(packages[i], mUser);
2754 mBgAllAppsList.updatePackage(context, packages[i], mUser);
2755 mApp.getWidgetCache().removePackage(packages[i], mUser);
2756 }
2757 break;
2758 case OP_REMOVE :
2759 {
2760 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2761 if (heuristic != null) {
2762 heuristic.processPackageRemoved(mPackages);
2763 }
2764 for (int i = 0; i < N; i++) {
2765 if (DEBUG_LOADERS) {
2766 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2767 }
2768 mIconCache.removeIconsForPkg(packages[i], mUser);
2769 }
2770 // Fall through
2771 }
2772 case OP_UNAVAILABLE :
2773 for (int i = 0; i < N; i++) {
2774 if (DEBUG_LOADERS) {
2775 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2776 }
2777 mBgAllAppsList.removePackage(packages[i], mUser);
2778 mApp.getWidgetCache().removePackage(packages[i], mUser);
2779 }
2780 break;
2781 }
2782 ArrayList<AppInfo> added = null;
2783 ArrayList<AppInfo> modified = null;
2784 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2785 if (mBgAllAppsList.added.size() > 0) {
2786 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2787 mBgAllAppsList.added.clear();
2788 }
2789 if (mBgAllAppsList.modified.size() > 0) {
2790 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2791 mBgAllAppsList.modified.clear();
2792 }
2793 if (mBgAllAppsList.removed.size() > 0) {
2794 removedApps.addAll(mBgAllAppsList.removed);
2795 mBgAllAppsList.removed.clear();
2796 }
2797 final Callbacks callbacks = getCallback();
2798 if (callbacks == null) {
2799 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2800 return;
2801 }
2802 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<ComponentName, AppInfo🔵
2803 if (added != null) {
2804 addAppsToAllApps(context, added);
2805 for (AppInfo ai : added) {
2806 addedOrUpdatedApps.put(ai.componentName, ai);
2807 }
2808 }
2809 if (modified != null) {
2810 final ArrayList<AppInfo> modifiedFinal = modified;
2811 for (AppInfo ai : modified) {
2812 addedOrUpdatedApps.put(ai.componentName, ai);
2813 }
2814 mHandler.post(new Runnable() {
2815 public void run() {
2816 Callbacks cb = getCallback();
2817 if ((callbacks == cb) && (cb != null)) {
2818 callbacks.bindAppsUpdated(modifiedFinal);
2819 }
2820 }
2821 });
2822 }
2823 // Update shortcut infos
2824 if ((mOp == OP_ADD) || (mOp == OP_UPDATE)) {
2825 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
2826 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
2827 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
2828 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
2829 synchronized(sBgLock) {
2830 for (ItemInfo info : sBgItemsIdMap) {
2831 if ((info instanceof ShortcutInfo) && mUser.equals(info.user)) {
2832 ShortcutInfo si = ((ShortcutInfo) (info));
2833 boolean infoUpdated = false;
2834 boolean shortcutUpdated = false;
2835 // Update shortcuts which use iconResource.
2836 if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageN🔵
2837 Bitmap icon = Utilities.createIconBitmap(si.iconResource.packageName, si.🔵
2838 if (icon != null) {
2839 si.setIcon(icon);
2840 si.usingFallbackIcon = false;
2841 infoUpdated = true;
2842 }
2843 }
2844 ComponentName cn = si.getTargetComponent();
2845 if ((cn != null) && packageSet.contains(cn.getPackageName())) {
2846 AppInfo appInfo = addedOrUpdatedApps.get(cn);
2847 if (si.isPromise()) {
2848 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
2849 // Auto install icon
2850 PackageManager pm = context.getPackageManager();
2851 ResolveInfo matched = pm.resolveActivity(new Intent(Intent.ACTION🔵
2852 if (matched == null) {
2853 // Try to find the best match activity.
2854 Intent intent = pm.getLaunchIntentForPackage(cn.getPackageNam🔵
2855 if (intent != null) {
2856 cn = intent.getComponent();
2857 appInfo = addedOrUpdatedApps.get(cn);
2858 }
2859 if ((intent == null) || (appInfo == null)) {
2860 removedShortcuts.add(si);
2861 continue;
2862 }
2863 si.promisedIntent = intent;
2864 }
2865 }
2866 // Restore the shortcut.
2867 if (appInfo != null) {
2868 si.flags = appInfo.flags;
2869 }
2870 si.intent = si.promisedIntent;
2871 si.promisedIntent = null;
2872 si.status = ShortcutInfo.DEFAULT;
2873 infoUpdated = true;
2874 si.updateIcon(mIconCache);
2875 }
2876 if (((appInfo != null) && Intent.ACTION_MAIN.equals(si.intent.getAction()🔵
2877 si.updateIcon(mIconCache);
2878 si.title = Utilities.trim(appInfo.title);
2879 si.contentDescription = appInfo.contentDescription;
2880 infoUpdated = true;
2881 }
2882 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
2883 // Since package was just updated, the target must be available now.
2884 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2885 shortcutUpdated = true;
2886 }
2887 }
2888 if (infoUpdated || shortcutUpdated) {
2889 updatedShortcuts.add(si);
2890 }
2891 if (infoUpdated) {
2892 updateItemInDatabase(context, si);
2893 }
2894 } else if (info instanceof LauncherAppWidgetInfo) {
2895 LauncherAppWidgetInfo widgetInfo = ((LauncherAppWidgetInfo) (info));
2896 if ((mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag(LauncherAppWi🔵
2897 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
2898 widgets.add(widgetInfo);
2899 updateItemInDatabase(context, widgetInfo);
2900 }
2901 }
2902 }
2903 }
2904 if ((!updatedShortcuts.isEmpty()) || (!removedShortcuts.isEmpty())) {
2905 mHandler.post(new Runnable() {
2906 public void run() {
2907 Callbacks cb = getCallback();
2908 if ((callbacks == cb) && (cb != null)) {
2909 callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, mUser)🔵
2910 }
2911 }
2912 });
2913 if (!removedShortcuts.isEmpty()) {
2914 deleteItemsFromDatabase(context, removedShortcuts);
2915 }
2916 }
2917 if (!widgets.isEmpty()) {
2918 mHandler.post(new Runnable() {
2919 public void run() {
2920 Callbacks cb = getCallback();
2921 if ((callbacks == cb) && (cb != null)) {
2922 callbacks.bindWidgetsRestored(widgets);
2923 }
2924 }
2925 });
2926 }
2927 }
2928 final ArrayList<String> removedPackageNames = new ArrayList<String>();
2929 if ((mOp == OP_REMOVE) || (mOp == OP_UNAVAILABLE)) {
2930 // Mark all packages in the broadcast to be removed
2931 removedPackageNames.addAll(Arrays.asList(packages));
2932 } else if (mOp == OP_UPDATE) {
2933 // Mark disabled packages in the broadcast to be removed
2934 for (int i = 0; i < N; i++) {
2935 if (isPackageDisabled(context, packages[i], mUser)) {
2936 removedPackageNames.add(packages[i]);
2937 }
2938 }
2939 }
2940 if ((!removedPackageNames.isEmpty()) || (!removedApps.isEmpty())) {
2941 final int removeReason;
2942 if (mOp == OP_UNAVAILABLE) {
2943 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2944 } else {
2945 // Remove all the components associated with this package
2946 for (String pn : removedPackageNames) {
2947 deletePackageFromDatabase(context, pn, mUser);
2948 }
2949 // Remove all the specific components
2950 for (AppInfo a : removedApps) {
2951 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
2952 deleteItemsFromDatabase(context, infos);
2953 }
2954 removeReason = 0;
2955 }
2956 // Remove any queued items from the install queue
2957 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
2958 // Call the components-removed callback
2959 mHandler.post(new Runnable() {
2960 public void run() {
2961 Callbacks cb = getCallback();
2962 if ((callbacks == cb) && (cb != null)) {
2963 callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser, remo🔵
2964 }
2965 }
2966 });
2967 }
2968 // onProvidersChanged method (API >= 17) already refreshed the widget list
2969 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
2970 // Write all the logs to disk
2971 mHandler.post(new Runnable() {
2972 public void run() {
2973 Callbacks cb = getCallback();
2974 if ((callbacks == cb) && (cb != null)) {
2975 callbacks.dumpLogsToLocalData();
2976 }
2977 }
2978 });
2979 }
2980 }
2981
2982 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
2983 boolean refresh) {
2984 ArrayList<LauncherAppWidgetProviderInfo> results =
2985 new ArrayList<LauncherAppWidgetProviderInfo>();
2986 try {
2987 synchronized (sBgLock) {
2988 if (sBgWidgetProviders == null || refresh) {
2989 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
2990 = new HashMap<>();
2991 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
2992 LauncherAppWidgetProviderInfo info;
2993
2994 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
2995 for (AppWidgetProviderInfo pInfo : widgets) {
2996 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
2997 UserHandleCompat user = wm.getUser(info);
2998 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
2999 }
3000
3001 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3002 for (CustomAppWidget widget : customWidgets) {
3003 info = new LauncherAppWidgetProviderInfo(context, widget);
3004 UserHandleCompat user = wm.getUser(info);
3005 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3006 }
3007 // Replace the global list at the very end, so that if there is an exception,
3008 // previously loaded provider list is used.
3009 sBgWidgetProviders = tmpWidgetProviders;
3010 }
3011 results.addAll(sBgWidgetProviders.values());
3012 return results;
3013 }
3014 } catch (Exception e) {
3015 if (e.getCause() instanceof TransactionTooLargeException) {
3016 // the returned value may be incomplete and will not be refreshed until the next
3017 // time Launcher starts.
3018 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3019 // onResume is called to refresh the widget provider list.
3020 synchronized (sBgLock) {
3021 if (sBgWidgetProviders != null) {
3022 results.addAll(sBgWidgetProviders.values());
3023 }
3024 return results;
3025 }
3026 } else {
3027 throw e;
3028 }
3029 }
3030 }
3031
3032 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3033 UserHandleCompat user) {
3034 synchronized (sBgLock) {
3035 if (sBgWidgetProviders == null) {
3036 getWidgetProviders(ctx, false /* refresh */);
3037 }
3038 return sBgWidgetProviders.get(new ComponentKey(name, user));
3039 }
3040 }
3041
3042 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks, final bo🔵
3043 runOnWorkerThread(new Runnable() {
3044 @Override
3045 public void run() {
3046 updateWidgetsModel(context, refresh);
3047 final WidgetsModel model = mBgWidgetsModel.clone();
3048 mHandler.post(new Runnable() {
3049 @Override
3050 public void run() {
3051 Callbacks cb = getCallback();
3052 if ((callbacks == cb) && (cb != null)) {
3053 callbacks.bindAllPackages(model);
3054 }
3055 }
3056 });
3057 // update the Widget entries inside DB on the worker thread.
3058 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(model.getRawList()🔵
3059 }
3060 });
3061 }
3062
3063 /**
3064 * Returns a list of ResolveInfos/AppWidgetInfos.
3065 *
3066 * @see #loadAndBindWidgetsAndShortcuts
3067 */
3068 @Thunk
3069 void updateWidgetsModel(Context context, boolean refresh) {
3070 PackageManager packageManager = context.getPackageManager();
3071 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3072 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3073 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3074 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3075 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3076 }
3077
3078 @Thunk
3079 static boolean isPackageDisabled(Context context, String packageName, UserHandleCompat user) {
3080 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3081 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3082 }
3083
3084 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3085 UserHandleCompat user) {
3086 if (cn == null) {
3087 return false;
3088 }
3089 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3090 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3091 return false;
3092 }
3093 return launcherApps.isActivityEnabledForProfile(cn, user);
3094 }
3095
3096 public static boolean isValidPackage(Context context, String packageName,
3097 UserHandleCompat user) {
3098 if (packageName == null) {
3099 return false;
3100 }
3101 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3102 return launcherApps.isPackageEnabledForProfile(packageName, user);
3103 }
3104
3105 /**
3106 * Make an ShortcutInfo object for a restored application or shortcut item that points
3107 * to a package that is not yet installed on the system.
3108 */
3109 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent, int promiseType, int🔵
3110 final ShortcutInfo info = new ShortcutInfo();
3111 info.user = UserHandleCompat.myUserHandle();
3112 Bitmap icon = iconInfo.loadIcon(c, info, context);
3113 // the fallback icon
3114 if (icon == null) {
3115 /* useLowResIcon */
3116 mIconCache.getTitleAndIcon(info, intent, info.user, false);
3117 } else {
3118 info.setIcon(icon);
3119 }
3120 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3121 String title = (c != null) ? c.getString(titleIndex) : null;
3122 if (!TextUtils.isEmpty(title)) {
3123 info.title = Utilities.trim(title);
3124 }
3125 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3126 if (TextUtils.isEmpty(info.title)) {
3127 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3128 }
3129 } else {
3130 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3131 }
3132 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3133 info.itemType = itemType;
3134 info.promisedIntent = intent;
3135 info.status = promiseType;
3136 return info;
3137 }
3138
3139 /**
3140 * Make an Intent object for a restored application or shortcut item that points
3141 * to the market page for the item.
3142 */
3143 @Thunk
3144 Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3145 ComponentName componentName = intent.getComponent();
3146 return getMarketIntent(componentName.getPackageName());
3147 }
3148
3149 static Intent getMarketIntent(String packageName) {
3150 return new Intent(Intent.ACTION_VIEW)
3151 .setData(new Uri.Builder()
3152 .scheme("market")
3153 .authority("details")
3154 .appendQueryParameter("id", packageName)
3155 .build());
3156 }
3157
3158 /**
3159 * Make an ShortcutInfo object for a shortcut that is an application.
3160 *
3161 * If c is not null, then it will be used to fill in missing data like the title and icon.
3162 */
3163 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent, UserHandleCompat user, 🔵
3164 if (user == null) {
3165 Log.d(TAG, "Null user found in getShortcutInfo");
3166 return null;
3167 }
3168 ComponentName componentName = intent.getComponent();
3169 if (componentName == null) {
3170 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3171 return null;
3172 }
3173 Intent newIntent = new Intent(intent.getAction(), null);
3174 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3175 newIntent.setComponent(componentName);
3176 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3177 if ((lai == null) && (!allowMissingTarget)) {
3178 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3179 return null;
3180 }
3181 final ShortcutInfo info = new ShortcutInfo();
3182 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3183 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && (c != null)) {
3184 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3185 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3186 }
3187 // from the db
3188 if (TextUtils.isEmpty(info.title) && (c != null)) {
3189 info.title = Utilities.trim(c.getString(titleIndex));
3190 }
3191 // fall back to the class name of the activity
3192 if (info.title == null) {
3193 info.title = componentName.getClassName();
3194 }
3195 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3196 info.user = user;
3197 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3198 if (lai != null) {
3199 info.flags = AppInfo.initFlags(lai);
3200 }
3201 return info;
3202 }
3203
3204 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, ItemInfoFilter f) {
3205 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3206 for (ItemInfo i : infos) {
3207 if (i instanceof ShortcutInfo) {
3208 ShortcutInfo info = ((ShortcutInfo) (i));
3209 ComponentName cn = info.getTargetComponent();
3210 if ((cn != null) && f.filterItem(null, info, cn)) {
3211 filtered.add(info);
3212 }
3213 } else if (i instanceof FolderInfo) {
3214 FolderInfo info = ((FolderInfo) (i));
3215 for (ShortcutInfo s : info.contents) {
3216 ComponentName cn = s.getTargetComponent();
3217 if ((cn != null) && f.filterItem(info, s, cn)) {
3218 filtered.add(s);
3219 }
3220 }
3221 } else if (i instanceof LauncherAppWidgetInfo) {
3222 LauncherAppWidgetInfo info = ((LauncherAppWidgetInfo) (i));
3223 ComponentName cn = info.providerName;
3224 if ((cn != null) && f.filterItem(null, info, cn)) {
3225 filtered.add(info);
3226 }
3227 }
3228 }
3229 return new ArrayList<ItemInfo>(filtered);
3230 }
3231
3232 @Thunk
3233 ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname, final UserHandleCompat use🔵
3234 ItemInfoFilter filter = new ItemInfoFilter() {
3235 @Override
3236 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3237 if (info.user == null) {
3238 return cn.equals(cname);
3239 } else {
3240 return cn.equals(cname) && info.user.equals(user);
3241 }
3242 }
3243 };
3244 return filterItemInfos(sBgItemsIdMap, filter);
3245 }
3246
3247 /**
3248 * Make an ShortcutInfo object for a shortcut that isn't an application.
3249 */
3250 @Thunk
3251 ShortcutInfo getShortcutInfo(Cursor c, Context context, int titleIndex, CursorIconInfo iconInfo) {
3252 final ShortcutInfo info = new ShortcutInfo();
3253 // Non-app shortcuts are only supported for current user.
3254 info.user = UserHandleCompat.myUserHandle();
3255 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3256 // TODO: If there's an explicit component and we can't install that, delete it.
3257 info.title = Utilities.trim(c.getString(titleIndex));
3258 Bitmap icon = iconInfo.loadIcon(c, info, context);
3259 // the fallback icon
3260 if (icon == null) {
3261 icon = mIconCache.getDefaultIcon(info.user);
3262 info.usingFallbackIcon = true;
3263 }
3264 info.setIcon(icon);
3265 return info;
3266 }
3267
3268 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3269 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3270 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3271 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3272 if (intent == null) {
3273 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3274 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3275 return null;
3276 }
3277 Bitmap icon = null;
3278 boolean customIcon = false;
3279 ShortcutIconResource iconResource = null;
3280 if (bitmap instanceof Bitmap) {
3281 icon = Utilities.createIconBitmap(((Bitmap) (bitmap)), context);
3282 customIcon = true;
3283 } else {
3284 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3285 if (extra instanceof ShortcutIconResource) {
3286 iconResource = ((ShortcutIconResource) (extra));
3287 icon = Utilities.createIconBitmap(iconResource.packageName, iconResource.resourceName, co🔵
3288 }
3289 }
3290 final ShortcutInfo info = new ShortcutInfo();
3291 // Only support intents for current user for now. Intents sent from other
3292 // users wouldn't get here without intent forwarding anyway.
3293 info.user = UserHandleCompat.myUserHandle();
3294 if (icon == null) {
3295 icon = mIconCache.getDefaultIcon(info.user);
3296 info.usingFallbackIcon = true;
3297 }
3298 info.setIcon(icon);
3299 info.title = Utilities.trim(name);
3300 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3301 info.intent = intent;
3302 info.customIcon = customIcon;
3303 info.iconResource = iconResource;
3304 return info;
3305 }
3306
3307 /**
3308 * Return an existing FolderInfo object if we have encountered this ID previously,
3309 * or make a new one.
3310 */
3311 @Thunk
3312 static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3313 // See if a placeholder was created for us already
3314 FolderInfo folderInfo = folders.get(id);
3315 if (folderInfo == null) {
3316 // No placeholder -- create a new instance
3317 folderInfo = new FolderInfo();
3318 folders.put(id, folderInfo);
3319 }
3320 return folderInfo;
3321 }
3322
3323 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3324 return (provider != null) && (provider.provider != null)
3325 && (provider.provider.getPackageName() != null);
3326 }
3327
3328 public void dumpState() {
3329 Log.d(TAG, "mCallbacks=" + mCallbacks);
3330 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3331 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3332 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3333 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3334 if (mLoaderTask != null) {
3335 mLoaderTask.dumpState();
3336 } else {
3337 Log.d(TAG, "mLoaderTask=null");
3338 }
3339 }
3340
3341 public Callbacks getCallback() {
3342 return mCallbacks != null ? mCallbacks.get() : null;
3343 }
3344
3345 /**
3346 * @return {@link FolderInfo} if its already loaded.
3347 */
3348 public FolderInfo findFolderById(Long folderId) {
3349 synchronized(sBgLock) {
3350 return sBgFolders.get(folderId);
3351 }
3352 }
3353
3354 /**
3355 * @return the looper for the worker thread which can be used to start background tasks.
3356 */
3357 public static Looper getWorkerLooper() {
3358 return sWorkerThread.getLooper();
3359 }
3360 }
|